java 中的阻塞队列及生产者-消费者中的简单应用

47 阅读3分钟

一、什么是阻塞队列

阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如图所示:

image.png

  • 当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
  • 当阻塞队列是满时,往队列中添加元素的操作将会被阻塞。
  • 同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他线程从队列中移除一个或者多个元素或者全清空队列后,使队列重新变得空闲起来并后续新增。

二、阻塞队列有什么好处

在多线程领域:所谓阻塞,在某些情况下会挂起线程(即线程阻塞),一旦条件满足,被挂起的线程又会被自动唤醒。
使用阻塞队列的好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为BlockingQueue都一手包办好了。
concurrent包发布以前,在多线程环境下,每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。

三、java 中的阻塞队列

3.1 架构介绍

image.png

3.2 种类分析

  1. ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
  2. LinkedBlockingQueue:由链表结构组成的有界(但大小默认值是Integer.MAX_VALUE)阻塞队列。
  3. PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
  4. DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
  5. SynchronousQueue:不存储元素的阻塞队列,也即是单个元素的队列。 (与其他BlcokingQueue不同,synchronousQueue是一个不存储元素的BlcokingQueue。每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。)
  6. LinkedTransferQueue:由链表结构组成的无界阻塞队列。
  7. LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

3.3 BlockingQueue 的核心方法

image.png

类型说明
抛出异常当阻塞队列满时,再往队列里面add插入元素会抛出 IllegalStateException:Queue full 当阻塞队列空时,再往队列 Remove 元素时候会抛出 NoSuchElementException
特殊值插入方法,成功返回true,失败返回 false 移除方法,成功返回元素,队列里面没有就返回 null
一直阻塞当阻塞队列满时,生产者继续往队列里面 put 元素,队列会一直阻塞,直到put数据或者响应中断退出。 当阻塞队列空时,消费者试图从队列 take 元素,队列会一直阻塞,消费者线程直到队列可用取出元素。
超时退出当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出。

四、生产者-消费者问题

4.1 传统的使用 await() 和 signal()

class ShareData {
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void increment() {
        lock.lock();
        try {
            while (number != 0) {
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + " " + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decrement() {
        lock.lock();
        try {
            while (number == 0) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + " " + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class ProdConsumer_traditionDemo {

    public static void main(String[] args) {
        ShareData shareData = new ShareData();

        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                shareData.increment();
            }
        }, "Thread-A").start();

        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                shareData.decrement();
            }
        }, "Thread-B").start();
    }
}

image.png

4.2 使用阻塞队列

class MyResource {
    private volatile boolean flag = true;
    private AtomicInteger atomicInteger = new AtomicInteger();

    private BlockingQueue<String> blockingQueue = null;

    public MyResource(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    public void producer() throws InterruptedException {
        String data = null;
        boolean resFlag;

        while (flag) {
            data = atomicInteger.incrementAndGet() + "";
            resFlag = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
            if (resFlag) {
                System.out.println(Thread.currentThread().getName() + " 生产队列, " + data + " 插入队列成功");
            } else {
                System.out.println(Thread.currentThread().getName() + " 生产队列, " + data + " 插入队列失败");
            }
            TimeUnit.SECONDS.sleep(1);
        }

        System.out.println(Thread.currentThread().getName() + "flag = false, 生产动作结束");
    }

    public void consumer() throws InterruptedException {
        String result = null;

        while (flag) {
            result = blockingQueue.poll(2, TimeUnit.SECONDS);

            if (result == null || result.equalsIgnoreCase("")) {
                flag = false;
                System.out.println(Thread.currentThread().getName() + " 超过2秒没有取到,停止消费");
                return;
            }

            System.out.println(Thread.currentThread().getName() + " 消费队列:" + result);
        }
    }

    public void stop() {
        this.flag = false;
    }
}

public class ProdConsumer_BlockQueueDemo {

    public static void main(String[] args) {
        MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));

        new Thread(() -> {
            System.out.println("生产线程启动...");
            try {
                myResource.producer();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "producer").start();

        new Thread(() -> {
            System.out.println("消费线程启动...");
            try {
                myResource.consumer();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "consumer").start();

        try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }

        myResource.stop();
        System.out.println(Thread.currentThread().getName() + " main 线程终止生产-消费");
    }
}

image.png