BlockingQueue-多线程安全集合

49 阅读4分钟

文章目录


前言

BlockingQueue是一个接口,是一个能够保证线程安全的队列;一般适用于生产-消费模式的场景;


一、生产消费模式?

该模式能够简化开发过程,一方面消除了生产者类与消费者类之间的代码依赖性,另一方面将生产数据的过程与使用数据的过程解耦简化负载。

我们自己coding实现这个模式的时候,因为需要让多个线程操作共享变量(即资源),所以很容易引发线程安全问题,造成重复消费和死锁,尤其是生产者和消费者存在多个的情况。另外,当缓冲池空了,我们需要阻塞消费者,唤醒生产者;当缓冲池满了,我们需要阻塞生产者,唤醒消费者,这些个等待- 唤醒逻辑都需要自己实现。

这么容易出错的事情,JDK当然帮我们做啦,这就是阻塞队列(BlockingQueue),你只管往里面存、取就行,而不用担心多线程环境下存、取共享变量的线程安全问题。

二、BlockingQueue的实现

  • ArrayBlockingQueue 由数组支持的有界队列
  • LinkedBlockingQueue 由链接节点支持的可选有界队列
  • PriorityBlockingQueue 由优先级堆支持的无界优先级队列
  • DelayQueue 由优先级堆支持的、基于时间的调度队列

有界无界的意思是 有没有队列的长度限制

方法|
说明
---|---

add()|
如果插入成功则返回 true,否则抛出 IllegalStateException 异常

put()|
将指定的元素插入队列,如果队列满了,那么会阻塞直到有空间插入

offer()|
如果插入成功则返回 true,否则返回 false

offer(E e, long timeout, TimeUnit unit)|
尝试将元素插入队列,如果队列已满,那么会阻塞直到有空间插入

方法|
说明
---|---

take()|
获取队列的头部元素并将其删除,如果队列为空,则阻塞并等待元素变为可用

poll(long timeout, TimeUnit unit)|
检索并删除队列的头部,如有必要,等待指定的等待时间以使元素可用,如果超时,则返回 null

三、实战

1 添加元素 : put

add offer

  • add(E e) :往队列插入数据,当队列满时,插入元素时会抛出IllegalStateException异常;

  • offer(E e):当往队列插入数据时,插入成功返回true,否则则返回false。当队列满时不会抛出异常;

  • put:当阻塞队列容量已经满时,往阻塞队列插入数据的线程会被阻塞,直至阻塞队列已经有空余的容量可供使用
    代码验证

    /**

    • @author fulin */ public class TestPark {

      ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(5);

      @SneakyThrows public static void main(String[] args) {

       TestPark testPark = new TestPark();
      
       new Thread(() -> {
           //arrayBlockingQueue.take();
           for (int i = 0; i < 6; i++) {
               try {
      

    // testPark.arrayBlockingQueue.put(RandomUtil.randomInt()); testPark.arrayBlockingQueue.add(RandomUtil.randomInt()); // boolean offer = testPark.arrayBlockingQueue.offer(RandomUtil.randomInt()); // System.out.println(offer); } catch (Exception e) { throw new RuntimeException(e); } } System.out.println(testPark.arrayBlockingQueue.size()); }).start();

        // 多线程 验证put 阻塞现象
    

    // Thread.sleep(6000); // new Thread(() -> { // testPark.arrayBlockingQueue.poll(); // }).start();

    }
    

    }

1 add 验证
2 put 验证
3 offer 验证: 一共想放入六个元素,但是集合只有五个,那么当插入第六个的时候,线程会被阻塞; 我们再次启动一个线程,然后将之前的集合中的一个元素移除掉,此时有了空闲位置,那么offer才会成功,所以第一个线程将会阻塞大概6s;
这就是offer 会阻塞的代码验证;

2 、删除元素:

remove poll take

  • remove(Object o):从队列中删除数据,成功则返回true,否则为false

  • poll:删除数据,当队列为空时,返回null;

  • take():当阻塞队列为空时,获取队头数据的线程会被阻塞;
    代码验证

    /**

    • @author fulin */ public class TestPark {

      ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(5);

      @SneakyThrows public static void main(String[] args) {

       TestPark testPark = new TestPark();
      
       // 多线程 验证take 阻塞现象
       new Thread(() -> {
           try {
               testPark.arrayBlockingQueue.take();
               System.out.println("take成功");
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
       }).start();
       Thread.sleep(2000);
      
       new Thread(() -> {
           //arrayBlockingQueue.take();
           for (int i = 0; i < 6; i++) {
               try {
                   testPark.arrayBlockingQueue.put(RandomUtil.randomInt());
      

    // testPark.arrayBlockingQueue.add(RandomUtil.randomInt()); // boolean offer = testPark.arrayBlockingQueue.offer(RandomUtil.randomInt()); // System.out.println(offer); } catch (Exception e) { throw new RuntimeException(e); } } System.out.println(testPark.arrayBlockingQueue.size()); }).start(); } }

1 remove poll
2 take 验证take获删除元素,当集合为空那么会被阻塞,当经过两秒之后,另一个线程启动,会将元素开始放入集合中,那么阻塞才会结束;


总结

当使用BlockingQueue的时候, 这里要注意的最重要的事情是 BlockingQueue 用于协调生产和消费者它们之间的工作。
一般来说 会使用 DelayQueue 实现生产消费的延迟场景的需求;
LinkedBlockingQueue 用它来实现有界队列的生产消费模式;
一般不推荐使用无界队列,因为出现问题后将一直导致oom;

本文转自 jimolvxing.blog.csdn.net/article/det…,如有侵权,请联系删除。