JDK-In-Action-BlockingQueue

阻塞队列 BlockingQueue

阻塞队列操作方法概览

-|Throws exception|Special value|Blocks|Times out
----|----|----|----|----|----|
Insert|add(e) |offer(e) |put(e) |offer(e, time, unit)|
Remove |remove() |poll() |take() |poll(time, unit)|
Examine |element() |peek() |not applicable |not applicable|

ArrayBlockingQueue

特性

  • 同步策略: 重入锁(可选公平/非公平)和非空(notEmpty), 非满(notFull)条件同步
    并发操作前需要先获取独占锁;添加元素后,非空条件唤醒;移除元素后,非满条件唤醒;
  • 内部数据结构: 固定大小的环型数组(数组的第一位和最后一位逻辑相邻)
    数组维护takeIndex用于移除/获取队首元素,putIndex用于添加队尾元素.当索引移动到数组最后时,从0再开始
    移除队首元素只需要移动索引,但是删除中间的元素则需要进行拷贝移动元素
  • 容量: 固定有界.初始构造时传入容量大小,同时创建数组分配内存
  • 顺序: 先进先出FIFO有序
  • 操作: 队头队尾元素操作在常数时间完成;支持阻塞或者超时的元素新增和移除
  • 迭代: 弱一致性,可以与其他操作并发执行,可以删除元素.不会抛出ConcurrentModificationException

结构示意图

1

三种构造函数

 //仅设置容量, 默认非公平模式
 BlockingQueue<Integer> blockingQueue1 = new ArrayBlockingQueue<>(4);
 //设置容量和锁竞争模式
 BlockingQueue<Integer> blockingQueue2 = new ArrayBlockingQueue<>(4, true);
 //从一个集合中初始化队列元素
 BlockingQueue<Integer> blockingQueue3 = new ArrayBlockingQueue<>(4, true, Arrays.asList(1, 2, 3, 4));

Draining 队列

当没有并发操作后,导出队列剩余的全部元素到集合

 BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(4, false, Arrays.asList(1, 2, 3, 4));
 ArrayList<Integer> list = new ArrayList<>();
 blockingQueue.drainTo(list);
 assertEquals(list, "[1, 2, 3, 4]");

超时式元素操作

 BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(2);
 ExecutorService executor = newCommonExecutor();
 final Integer loop = 3;
 executor.submit(() -> {
     try {
         int n = loop;
         while (n-- > 0) {
             //获取并移除队首元素,空队列则等待
             Integer value = blockingQueue.poll(1, TimeUnit.SECONDS);
             log("poll:" + value);
         }
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 });
 executor.submit(() -> {
     try {
         Random random = new Random();
         int n = loop;
         while (n-- > 0) {
             int value = random.nextInt(100);
             //队尾添加元素,满队列则等待
             blockingQueue.offer(value, 1, TimeUnit.SECONDS);
             log("offer:" + value);
         }
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 });
 shutdownAndAwaitStop(executor);
17:43:52.297 Thread[pool-1-thread-1,5,main] poll:87
17:43:52.297 Thread[pool-1-thread-2,5,main] offer:87
17:43:52.330 Thread[pool-1-thread-1,5,main] poll:80
17:43:52.330 Thread[pool-1-thread-2,5,main] offer:80
17:43:52.330 Thread[pool-1-thread-2,5,main] offer:57
17:43:52.330 Thread[pool-1-thread-1,5,main] poll:57

阻塞式元素操作

 BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(4);
 ExecutorService executor = newCommonExecutor();
 final Integer loop = 5;
 executor.submit(() -> {
     try {
         int n = loop;
         while (n-- > 0) {
             //获取并移除队首元素,空队列则等待
             Integer value = blockingQueue.take();
             log("take:" + value);
         }
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 });
 executor.submit(() -> {
     try {
         Random random = new Random();
         int n = loop;
         while (n-- > 0) {
             int value = random.nextInt(100);
             //队尾添加元素,满队列则等待
             blockingQueue.put(value);
             log("put:" + value);
         }
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 });
 shutdownAndAwaitStop(executor);
17:43:52.340 Thread[pool-2-thread-2,5,main] put:74
17:43:52.341 Thread[pool-2-thread-2,5,main] put:58
17:43:52.340 Thread[pool-2-thread-1,5,main] take:74
17:43:52.341 Thread[pool-2-thread-2,5,main] put:44
17:43:52.341 Thread[pool-2-thread-1,5,main] take:58
17:43:52.341 Thread[pool-2-thread-2,5,main] put:84
17:43:52.341 Thread[pool-2-thread-2,5,main] put:24
17:43:52.341 Thread[pool-2-thread-1,5,main] take:44
17:43:52.342 Thread[pool-2-thread-1,5,main] take:84
17:43:52.342 Thread[pool-2-thread-1,5,main] take:24

LinkedBlockingQueue

特性

  • 数据结构: 固定容量的单向链表
  • 同步策略: 使用两个重入锁和对应的套件同步
    使用takeLock重入锁和非空(notEmpty)条件用于take, poll等操作的同步.
    使用putLock重入锁和非满(notFull)条件用于put, offer等操作的同步.
    • 新增元素时, 获取putLock锁, 自旋等待非满条件(若定义超时, 则等待超时时间), 然后添加元素, 如果添加的是当前队列第一个元素则唤醒非空条件;
    • 移除元素时, 获取takeLock锁, 自旋等待非空条件(若定义超时, 则等待超时时间), 然后移除元素, 如果还有剩余元素, 唤醒非空条件;如果是自满容量后第一个移除的元素, 唤醒非满条件;
  • 顺序: FIFO有序
  • 迭代: 弱一致性,可以与其他操作并发执行,可以删除元素.不会抛出ConcurrentModificationException

结构示意图

2

API结果和ArrayBlockingQueue一致,可以直接套用前面ArrayBlockingQueue的示例

二种构造函数

 //设置容量
 BlockingQueue<Integer> blockingQueue1 = new LinkedBlockingQueue<>(4);
 //从一个集合中初始化队列元素
 BlockingQueue<Integer> blockingQueue3 = new LinkedBlockingQueue<>(Arrays.asList(1, 2, 3, 4));

PriorityBlockingQueue

特性

  • 数据结构: 无界线性表结构的平衡二叉堆(最小堆/最大堆)
  • 同步策略: 使用单个重入锁和非空(notEmpty)条件同步
  • 顺序: 出队有序(最大/最小), 元素排序算法和PriorityQueue一致, 参见JDK-In-Action-PriorityQueue
  • 迭代: 弱一致性,可以并发操作,永远不会抛出ConcurrentModificationException,迭代无序

结构示意图

3

构造方法

 //构造一个容量为4的优先级队列,按自然顺序排序
 PriorityBlockingQueue<Integer> priorityBlockingQueue1 = new PriorityBlockingQueue<>(4);

 //提供一个比较器,按整数逆序排序
 PriorityBlockingQueue<Integer> priorityBlockingQueue2 = new PriorityBlockingQueue<>(4, (i1, i2) -> i2 - i1);

 //从集合构造优先级队列
 final List<Integer> list = Arrays.asList(1, 5, 2, 6, 8);
 PriorityBlockingQueue<Integer> priorityBlockingQueue3 = new PriorityBlockingQueue<>(list);
 assertEquals(priorityBlockingQueue3.toString(), "[1, 5, 2, 6, 8]");

有序地读队首元素

 BlockingQueue<Integer> blockingQueue = new PriorityBlockingQueue<>(4);
 try {
     blockingQueue.put(3);
     blockingQueue.put(5);
     blockingQueue.put(2);
     blockingQueue.put(1);
 } catch (InterruptedException e) {
     e.printStackTrace();
 }
 Integer value = blockingQueue.poll();
 while (value != null) {
     log("poll:" + value);
     value = blockingQueue.poll();
 }
19:51:57.175 Thread[main,5,main] poll:1
19:51:57.175 Thread[main,5,main] poll:2
19:51:57.175 Thread[main,5,main] poll:3
19:51:57.175 Thread[main,5,main] poll:5

基于优先级队列的生产者消费者模型

生产者无序推送元素,消费者优先获取小元素

 BlockingQueue<Integer> blockingQueue = new PriorityBlockingQueue<>(4);
 ExecutorService executor = newCommonExecutor();
 final Integer loop = 5;
 executor.submit(() -> {
     try {
         int n = loop;
         while (n-- > 0) {
             //获取并移除队首元素,空队列则等待
             Integer value = blockingQueue.take();
             log("take:" + value);
             sleep(100);
         }
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 });
 executor.submit(() -> {
     try {
         Random random = new Random();
         int n = loop;
         while (n-- > 0) {
             int value = random.nextInt(100);
             //队尾添加元素,满队列则等待
             blockingQueue.put(value);
             log("put:" + value);
         }
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 });
 shutdownAndAwaitStop(executor);
19:51:56.651 Thread[pool-1-thread-1,5,main] take:52
19:51:56.651 Thread[pool-1-thread-2,5,main] put:52
19:51:56.671 Thread[pool-1-thread-2,5,main] put:62
19:51:56.671 Thread[pool-1-thread-2,5,main] put:29
19:51:56.671 Thread[pool-1-thread-2,5,main] put:96
19:51:56.671 Thread[pool-1-thread-2,5,main] put:77
19:51:56.771 Thread[pool-1-thread-1,5,main] take:29
19:51:56.872 Thread[pool-1-thread-1,5,main] take:62
19:51:56.972 Thread[pool-1-thread-1,5,main] take:77
19:51:57.073 Thread[pool-1-thread-1,5,main] take:96

DelayQueue

特性

  • 内部数据结构: 优先级队列(平衡二叉堆)
  • 同步策略: 使用ReentrantLock同步
  • 容量: 无界,所以put,offer不会阻塞
  • 顺序: 队列根据getDelay()方法返回的剩余延迟时间对其元素进行排序. 队列的头部包含剩余延迟时间最少的元素. 队列的尾部包含剩余延迟时间最长的元素.
  • 操作:
  • 迭代器: 弱一致性,无序

实现细节

  • 队列元素必须实现Delayed接口提供一个获取延时时间的方法getDelay
  • 领导者与跟随者: leader指定为等待队列头部元素的线程. 领导者-跟随者模式的这种变体可以最小化不必要的定时等待.当一个线程成为leader时, 它只等待下一次延迟的到来, 而其他线程则无限期地等待.
  • offer: 插入元素; 首先获取独占锁,插入元素,如果插入元素是最小的,释放leader,唤醒其他等待线程.否则直接返回true(插入成功)
  • poll: 检索和删除队列中过期的元素,如果不存在则返回null.获取独占锁,peek优先队列的堆顶元素,判断是否过期,没有则返回null,否则移除并返回堆顶元素
  • take: 检索和删除队列中过期的元素;如果不存在则等待队列可用;如果未过期则计算剩余等待的时间然后等待或者超时等待,等待前判断能否成为leader,成为leader则超时等待,否则始终等待直到被唤醒;

延时队列调度

 DelayQueue<DT> delayQueue = new DelayQueue<>();
 Arrays.asList(12, 15, 14, 16).stream().forEach(t -> {
     delayQueue.put(new DT(t));
 });

 try {
     DT top = delayQueue.poll(3000, TimeUnit.MILLISECONDS);
     while (top != null) {
         log("run:" + top);
         top = delayQueue.poll(3000, TimeUnit.MILLISECONDS);
     }
 } catch (InterruptedException e) {
     e.printStackTrace();
 }
21:06:32.962 Thread[main,5,main] run:[12]
21:06:33.114 Thread[main,5,main] run:[14]
21:06:33.214 Thread[main,5,main] run:[15]
21:06:33.315 Thread[main,5,main] run:[16]

SynchronousQueue

特性

  • 无容量的阻塞同步队列
  • 生产者投递元素必须有一个消费者接受才返回,否则阻塞;元素直接被转移到消费者,不经过队列存储;
  • 顺序: 可选公平模式(用队列排队线程)或者非公平模式(用栈排队线程)

基于同步队列的生产者和消费者

可以看到输出文本中,每一个take后紧跟着一个take

 final SynchronousQueue<Integer> synchronousQueue = new SynchronousQueue<>();
 final ExecutorService executor = newCommonExecutor();
 final Integer loop = 2;
 executor.submit(() -> {
     try {
         Random random = new Random();
         int n = loop;
         while (n-- > 0) {
             int value = random.nextInt(100);
             synchronousQueue.put(value);
             log("put:" + value);
         }
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 });
 executor.submit(() -> {
     try {
         int n = loop;
         while (n-- > 0) {
             Integer value = synchronousQueue.take();
             log("take:" + value);
             sleep(100);
         }
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 });
 shutdownAndAwaitStop(executor);
21:15:33.081 Thread[pool-1-thread-1,5,main] put:56
21:15:33.081 Thread[pool-1-thread-2,5,main] take:56
21:15:33.202 Thread[pool-1-thread-1,5,main] put:53
21:15:33.202 Thread[pool-1-thread-2,5,main] take:53

TransferQueue

一种特殊的阻塞队列, 生产者可能等待消费者处理.在消息传递的应用程序中非常有用, 使用transfer方法投递元素, 但是仅当此前所有元素全部消费后, 该方法才返回.
一个容量为0的TransferQueuetransfer方法和SynchronousQueueput等同.

特殊API

  • boolean tryTransfer(): 如果可能, 立即将元素传输给等待的使用者. 更精确地说, 如果存在一个消费者已经在等待接收指定的元素(在take或timed poll中), 则立即传输该元素, 否则将返回false, 而不会对该元素进行排队.
  • void transfer() throws InterruptedException: 将元素传输给使用者, 如有必要则等待. 更精确地说, 如果存在一个消费者已经在等待接收指定的元素(在take或timed poll中), 则立即传输指定的元素, 否则将等待, 直到该元素被消费者接收.
  • boolean hasWaitingConsumer(): 如果至少有一个消费者在等待通过take或timed pool接收元素, 则返回true. 返回值代表事件的瞬间状态.
  • int getWaitingConsumerCount(): 返回通过take或定时轮询等待接收元素的消费者数量的估计值. 返回值是事件瞬间状态的近似值, 如果消费者已经完成或放弃等待, 则返回值可能不准确. 该值对于监视和启发可能有用, 但对于同步控制则没用. 此方法的实现可能比hasWaitingConsumer的实现要慢得多.

LinkedTransferQueue

特性

  • 数据结构: 无界的无锁双端队列
  • 顺序: FIFO
  • 特殊的,size()方法不是一个常量时间操作,因为队列的异步性,确定元素个数需要遍历所有元素,如果期间发生修改,报告结果将不准确
  • 不保证批量操作addAll、removeAll、retainAll、containsAll、equals和toArray以原子方式执行.
    例如,与addAll操作并发操作的迭代器可能只查看添加的部分元素
  • 元素不允许为null
  • 迭代: 弱一致性

分批确保交付生产消费

每个批次发送三个数据,同时确保每个批次被消费后才发送下一批次

 final TransferQueue<Integer> queue = new LinkedTransferQueue<>();
 Thread t1 = new Thread(() -> {
     try {
         for (int i = 0; i < 3; i++) {
             queue.put(1);
             queue.put(2);
             queue.transfer(3);
             System.out.println("transfer----done");
         }
     } catch (Exception e) {
         e.printStackTrace();
     }
 });

 Thread t2 = new Thread(() -> {
     try {
         for (int i = 0; i < 9; i++) {
             System.out.println("take:" + queue.take());
         }
     } catch (Exception e) {
         e.printStackTrace();
     }
 });

 t1.start();
 t2.start();

 t1.join();
 t2.join();
take:1
take:2
take:3
transfer----done
take:1
take:2
take:3
transfer----done
take:1
take:2
take:3
transfer----done

阻塞双端队列 BlockingDeque

双端队列操作汇总

  • First Element (Head)
- Throws exception Special value Blocks Times out
Insert addFirst(e) offerFirst(e) putFirst(e) offerFirst(e, time, unit)
Remove removeFirst() pollFirst() takeFirst() pollFirst(time, unit)
Examine getFirst() peekFirst() not applicable not applicable
  • Last Element (Tail)
- Throws exception Special value Blocks Times out
Insert addLast(e) offerLast(e) putLast(e) offerLast(e, time, unit)
Remove removeLast() pollLast() takeLast() pollLast(time, unit)
Examine getLast() peekLast() not applicable not applicable

也阻塞队列API的方法相当与双端队列的First Element (Head)系列方法(见上文阻塞队列)

LinkedBlockingDeque

特性

  • 内部数据结构: 有界双向链表
  • 同步策略: 使用重入锁(可选公平/非公平)和非空(notEmpty), 非满(notFull)条件同步.
  • 可以从队首或者队尾操作队列,队列满或者队列空时,响应的操作阻塞等待必要条件

结构图

LinkedBlockingDeque

阻塞式操作双端队列

 LinkedBlockingDeque<Integer> deque = new LinkedBlockingDeque(8);

 int loop = 4;
 try {
     int n = loop;
     while (n-- > 0) {
         deque.putFirst(n);
     }
 } catch (InterruptedException e) {
     e.printStackTrace();
 }

 try {
     int n = loop;
     while (n-- > 0) {
         deque.putLast(n * 100 + 1);
     }
 } catch (InterruptedException e) {
     e.printStackTrace();
 }
 log("Queue:" + deque.toString());


 try {
     int n = loop * 2;
     while (n-- > 0) {
         if (n % 2 == 0) {
             final Integer last = deque.pollFirst(3, TimeUnit.SECONDS);
             if (last == null) break;
             log("pollFirst:" + last);
         } else {
             final Integer first = deque.pollLast(3, TimeUnit.SECONDS);
             if (first == null) break;
             log("pollLast:" + first);
         }
     }
 } catch (InterruptedException e) {
     e.printStackTrace();
 }
22:06:06.961 Thread[main,5,main] Queue:[0, 1, 2, 3, 301, 201, 101, 1]
22:06:06.981 Thread[main,5,main] pollLast:1
22:06:06.981 Thread[main,5,main] pollFirst:0
22:06:06.981 Thread[main,5,main] pollLast:101
22:06:06.981 Thread[main,5,main] pollFirst:1
22:06:06.981 Thread[main,5,main] pollLast:201
22:06:06.981 Thread[main,5,main] pollFirst:2
22:06:06.982 Thread[main,5,main] pollLast:301
22:06:06.982 Thread[main,5,main] pollFirst:3

引用

posted @ 2020-05-13 22:20  onion94  阅读(174)  评论(0编辑  收藏  举报