多线程-阻塞队列(超详细)

目录

1.阻塞队列是什么

 生产者-消费者模型

2.标准库中的阻塞队列

 ⽣产者-消费者模型 

 阻塞队列实现


1.阻塞队列是什么

         阻塞队列(Blocking Queue)是一种特殊类型的队列,它在插入和删除元素时可以提供阻塞机制。阻塞队列能是⼀种线程安全的数据结构, 并且具有以下特性:

当队列满的时候, 继续⼊队列就会阻塞, 直到有其他线程从队列中取⾛元素.
当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插⼊元素.

生产者-消费者模型

      阻塞队列的⼀个典型应⽤场景就是 "⽣产者消费者模型". 这是⼀种⾮常典型的开发模型. ⽣产者消费者模式就是通过⼀个容器来解决⽣产者和消费者的强耦合问题。 ⽣产者和消费者彼此之间不直接通讯,⽽通过阻塞队列来进⾏通讯,所以⽣产者⽣产完数据之后不⽤ 等待消费者处理,直接扔给阻塞队列,消费者不找⽣产者要数据,⽽是直接从阻塞队列⾥取.

  1. 阻塞队列就相当于⼀个缓冲区,平衡了⽣产者和消费者的处理能⼒. (削峰填⾕)

      就像三峡大坝一样。它是为了抵御突然的洪水修建的。洪水突然来临的时候,三峡大坝可以缓解巨大的洪水量,将它储蓄起来。经过三峡大坝缓冲后,流到下流的水就不会特别大。当长时间干旱没有下雨的时候,三峡大坝就可以开闸放水,来填补。

   2. 阻塞队列也能使⽣产者和消费者之间 解耦

      ⽐如过年⼀家⼈⼀起包饺⼦. ⼀般都是有明确分⼯, ⽐如⼀个⼈负责擀饺⼦⽪, 其他⼈负责包. 擀饺⼦⽪的⼈就是 "⽣产者", 包饺⼦的⼈就是 "消费者". 擀饺⼦⽪的⼈不关⼼包饺⼦的⼈是谁(能包就⾏, ⽆论是⼿⼯包, 借助⼯具, 还是机器包), 包饺⼦的⼈也 不关⼼擀饺⼦⽪的⼈是谁(有饺⼦⽪就⾏, ⽆论是⽤擀⾯杖擀的, 还是拿罐头瓶擀, 还是直接从超市买的)

2.标准库中的阻塞队列

       在 Java 标准库中内置了阻塞队列. 如果我们需要在⼀些程序中使⽤阻塞队列, 直接使⽤标准库中的即可.
BlockingQueue 是⼀个接⼝. 真正实现的类是 LinkedBlockingQueue.
put ⽅法⽤于阻塞式的⼊队列, take ⽤于阻塞式的出队列.
BlockingQueue 也有 offer, poll, peek 等⽅法, 但是这些⽅法不带有阻塞特性.
BlockingQueue queue = new LinkedBlockingQueue<>();
// ⼊队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞. 
String elem = queue.take();

   ⽣产者消费者模型 

        生产者线程使用随机数生成器随机产生元素,并将其放入阻塞队列中。如果队列已满,则生产者线程会阻塞等待,直到有空闲位置可供插入。消费者线程从阻塞队列中获取元素,并进行相应的处理。如果队列为空,则消费者线程会阻塞等待,直到有数据可供消费。

        通过使用阻塞队列,生产者和消费者之间实现了解耦,避免了线程之间的竞争和忙等待,提高了系统的效率和性能。同时,阻塞队列提供了线程安全的插入和移除操作,保证了数据的有序处理和线程间的协调。

        在主函数中,启动了一个生产者线程和一个消费者线程,并使用 join() 方法等待它们的执行完成。这样可以确保程序在所有线程都执行完毕后退出,避免产生未处理的异常。

public static void main(String[] args) throws InterruptedException {
 BlockingQueue blockingQueue = new LinkedBlockingQueue();
 Thread customer = new Thread(() -> {
 while (true) {
 try {
 int value = blockingQueue.take();
 System.out.println("消费元素: " + value);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }, "消费者");
 customer.start();
 Thread producer = new Thread(() -> {
 Random random = new Random();
 while (true) {
try {
 int num = random.nextInt(1000);
 System.out.println("⽣产元素: " + num);
 blockingQueue.put(num);
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }, "⽣产者");
 producer.start();
 customer.join();
 producer.join();
}

 阻塞队列实现

通过 "循环队列" 的⽅式来实现.
使⽤ synchronized 进⾏加锁控制.
put 插⼊元素的时候, 判定如果队列满了, 就进⾏ wait. (注意, 要在循环中进⾏ wait. 被唤醒时不⼀定
队列就不满了, 因为同时可能是唤醒了多个线程).
take 取出元素的时候, 判定如果队列为空, 就进⾏ wait. (也是循环 wait)

  首先MyBlockingQueue 使用一个字符串数组 elems 来存储元素,同时维护了头指针 head、尾指针 tail 和队列中元素的个数 size。通过 synchronized 关键字和一个锁对象 locker,实现了线程安全的入队和出队操作。

  再用put 方法用于向队列中添加元素。如果队列已满,则调用 locker.wait() 阻塞当前线程,直到有空闲位置可供插入。然后将元素放入队列尾部,并更新尾指针和队列大小。最后调用 locker.notify() 唤醒其他可能正在等待的线程。

  take 方法用于从队列中取出元素。如果队列为空,则调用 locker.wait() 阻塞当前线程,直到有元素可供取出。然后取出头指针位置的元素,并更新头指针和队列大小。最后调用 locker.notify() 唤醒其他可能正在等待的线程,并返回取出的元素。

       最后在主函数中,创建了一个容量为 1000 的 MyBlockingQueue 对象,并依次向队列中放入了四个元素。然后使用 take 方法依次取出队列中的元素,并打印输出。

package 多线程;


//为了简单,不写作泛型的形式,考虑存储的元素就是单纯的String
    class MyBlockingQueue{

        private  String[] elems = null;

        private  int head = 0;

        private  int tail = 0;

        private  int size = 0;
        //准备一个锁对象
        private Object locker = new Object();


    public  MyBlockingQueue(int capacity){
        elems = new String[capacity];
    }

    public void put(String elem) throws InterruptedException {
        synchronized (locker){// 此处最好使⽤ while.
       // 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,
       // 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能⼜已经队列满了
       // 就只能继续等待
            while (size >= elems.length){
                //队列满了
                locker.wait();
                //后续需要让这个代码能够阻塞;
                return;
            }
            //新的元素要放到tail 指向的位置上
            elems[tail] = elem;
            tail++;
            if(tail >= elems.length){
                tail = 0;
            }
            size++;

            locker.notify();//入队列成功后唤醒
        }
    }//写操作

    public String take() throws InterruptedException {
        String elem = null;
        synchronized (locker){
            while (size == 0){
                //队列空了
                locker.wait();
                // 后续也需要让这个代码阻塞

            }
            //取出 head 位置的元素并 返回
            elem = elems[head];
            head++;
            if (head >= elems.length){
                head = 0;

            }
            size--;
            locker.notify();//出队列成功后唤醒

        }
        return elem;
        }

}

public class ThreadDemo24 {
    public static void main(String[] args) throws InterruptedException {
        MyBlockingQueue queue = new MyBlockingQueue(1000);

        queue.put("aaa");
        queue.put("bbb");
        queue.put("ccc");
        queue.put("vvv");

        String elem = "";
        elem = queue.take();
        System.out.println("elem: " + elem);
        elem = queue.take();
        System.out.println("elem: " + elem);
        elem = queue.take();
        System.out.println("elem: " + elem);
        elem = queue.take();
        System.out.println("elem: " + elem);
    }
}

运行结果 

多线程-阻塞队列(超详细)_第1张图片

          希望大家多多支持!!! 多线程-阻塞队列(超详细)_第2张图片

你可能感兴趣的:(java,java,开发语言)