1 简介

SynchronousQueue是这样一种阻塞队列,其中每个put必须等待一个take,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行peek,因为仅在试图要取得元素时,该元素才存在,除非另一个线程试图移除某个元素,否则也不能(使用任何方法)添加元素,也不能迭代队列,因为其中没有元素可用于迭代。队列的头是尝试添加到队列中的首个已排队线程元素,如果没有已排队线程,则不添加元素并且头为 null。
对于其他Collection方法(例如 contains),SynchronousQueue作为一个空集合,此队列不允许 null 元素。
同步队列类似于CSP和Ada中使用的rendezvous信道。它非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。
对于正在等待的生产者和使用者线程而言,此类支持可选的公平排序策略,默认情况下不保证这种排序。
但是,使用公平设置为true所构造的队列可保证线程以FIFO的顺序进行访问。公平通常会降低吞吐量,但是可以减小可变性并避免得不到服务。

2 使用示例

  1. import static org.junit.Assert.assertEquals;
  2.  
  3. import java.util.concurrent.CountDownLatch;
  4. import java.util.concurrent.ExecutorService;
  5. import java.util.concurrent.Executors;
  6. import java.util.concurrent.SynchronousQueue;
  7. import java.util.concurrent.ThreadLocalRandom;
  8. import java.util.concurrent.TimeUnit;
  9. import java.util.concurrent.atomic.AtomicInteger;
  10.  
  11. import org.junit.Test;
  12.  
  13. /**
  14. * synchronousqueue的使用场景 ==== 线程间共享元素
  15. * 假设有两个线程,一个生产者和一个消费者,当生产者设置一个共享变量的值时,我们希望向消费者线程
  16. * 发出这个信号,然后消费者线程将从共享变量取值。
  17. * @author ko
  18. *
  19. */
  20. public class Sqt {
  21.  
  22. /**
  23. * 利用AtomicInteger+CountDownLatch实现
  24. */
  25. @Test
  26. public void doingByCountDownLatch(){
  27. ExecutorService executor = Executors.newFixedThreadPool(2);
  28. AtomicInteger sharedState = new AtomicInteger();// 共享变量值
  29. // 协调这两个线程,以防止情况当消费者访问共享变量值
  30. CountDownLatch countDownLatch = new CountDownLatch(1);
  31.  
  32. // 生产商将设置一个随机整数到sharedstate变量,并countdown()方法,
  33. // 信号给消费者,它可以从sharedstate取一个值
  34. Runnable producer = () -> {// 这好像是java8的匿名内部类的新写法
  35. Integer producedElement = ThreadLocalRandom.current().nextInt();
  36. sharedState.set(producedElement);
  37. System.out.println("生产者给变量设值:"+producedElement);
  38. countDownLatch.countDown();
  39. };
  40.  
  41. // 消费者会等待countdownlatch执行到await()方法,获取许可后,再从生产者里获取变量sharedstate值
  42. Runnable consumer = () -> {
  43. try {
  44. countDownLatch.await();
  45. Integer consumedElement = sharedState.get();
  46. System.out.println("消费者获取到变量:"+consumedElement);
  47. } catch (InterruptedException ex) {
  48. ex.printStackTrace();
  49. }
  50. };
  51.  
  52. executor.execute(producer);
  53. executor.execute(consumer);
  54. try {
  55. executor.awaitTermination(500, TimeUnit.MILLISECONDS);
  56. } catch (InterruptedException e) {
  57. e.printStackTrace();
  58. }
  59. executor.shutdown();
  60. assertEquals(countDownLatch.getCount(), 0);
  61. }
  62.  
  63. /**
  64. * 仅使用SynchronousQueue就可以实现
  65. */
  66. @Test
  67. public void doingBySynchronousQueue(){
  68. ExecutorService executor = Executors.newFixedThreadPool(2);
  69. SynchronousQueue<Integer> queue = new SynchronousQueue<>();
  70.  
  71. // 生产者
  72. Runnable producer = () -> {
  73. Integer producedElement = ThreadLocalRandom.current().nextInt();
  74. try {
  75. queue.put(producedElement);
  76. System.out.println("生产者设值:"+producedElement);
  77. } catch (InterruptedException ex) {
  78. ex.printStackTrace();
  79. }
  80. };
  81.  
  82. // 消费者
  83. Runnable consumer = () -> {
  84. try {
  85. Integer consumedElement = queue.take();
  86. System.out.println("消费者取值:"+consumedElement);
  87. } catch (InterruptedException ex) {
  88. ex.printStackTrace();
  89. }
  90. };
  91.  
  92. executor.execute(producer);
  93. executor.execute(consumer);
  94. try {
  95. executor.awaitTermination(500, TimeUnit.MILLISECONDS);
  96. } catch (InterruptedException e) {
  97. e.printStackTrace();
  98. }
  99. executor.shutdown();
  100. assertEquals(queue.size(), 0);
  101. }
  102. }

3 实现原理

阻塞队列的实现方法有许多。

3.1 阻塞算法实现

阻塞算法实现通常在内部采用一个锁来保证多个线程中的put()和take()方法是串行执行的。采用锁的开销是比较大的,还会存在一种情况是线程A持有线程B需要的锁,B必须一直等待A释放锁,即使A可能一段时间内因为B的优先级比较高而得不到时间片运行。所以在高性能的应用中我们常常希望规避锁的使用。

  1. public class NativeSynchronousQueue<E> {
  2. boolean putting = false;
  3. E item = null;
  4.  
  5. public synchronized E take() throws InterruptedException {
  6. while (item == null)
  7. wait();
  8. E e = item;
  9. item = null;
  10. notifyAll();
  11. return e;
  12. }
  13.  
  14. public synchronized void put(E e) throws InterruptedException {
  15. if (e==null) return;
  16. while (putting)
  17. wait();
  18. putting = true;
  19. item = e;
  20. notifyAll();
  21. while (item!=null)
  22. wait();
  23. putting = false;
  24. notifyAll();
  25. }
  26. }

3.2 信号量实现

经典同步队列实现采用了三个信号量,代码很简单,比较容易理解。

  1. public class SemaphoreSynchronousQueue<E> {
  2. E item = null;
  3. Semaphore sync = new Semaphore(0);
  4. Semaphore send = new Semaphore(1);
  5. Semaphore recv = new Semaphore(0);
  6.  
  7. public E take() throws InterruptedException {
  8. recv.acquire();
  9. E x = item;
  10. sync.release();
  11. send.release();
  12. return x;
  13. }
  14.  
  15. public void put (E x) throws InterruptedException{
  16. send.acquire();
  17. item = x;
  18. recv.release();
  19. sync.acquire();
  20. }
  21. }

在多核机器上,上面方法的同步代价仍然较高,操作系统调度器需要上千个时间片来阻塞或唤醒线程,而上面的实现即使在生产者put()时已经有一个消费者在等待的情况下,阻塞和唤醒的调用仍然需要。

3.3 Java 5实现

  1. public class Java5SynchronousQueue<E> {
  2. ReentrantLock qlock = new ReentrantLock();
  3. Queue waitingProducers = new Queue();
  4. Queue waitingConsumers = new Queue();
  5.  
  6. static class Node extends AbstractQueuedSynchronizer {
  7. E item;
  8. Node next;
  9.  
  10. Node(Object x) { item = x; }
  11. void waitForTake() { /* (uses AQS) */ }
  12. E waitForPut() { /* (uses AQS) */ }
  13. }
  14.  
  15. public E take() {
  16. Node node;
  17. boolean mustWait;
  18. qlock.lock();
  19. node = waitingProducers.pop();
  20. if(mustWait = (node == null))
  21. node = waitingConsumers.push(null);
  22. qlock.unlock();
  23.  
  24. if (mustWait)
  25. return node.waitForPut();
  26. else
  27. return node.item;
  28. }
  29.  
  30. public void put(E e) {
  31. Node node;
  32. boolean mustWait;
  33. qlock.lock();
  34. node = waitingConsumers.pop();
  35. if (mustWait = (node == null))
  36. node = waitingProducers.push(e);
  37. qlock.unlock();
  38.  
  39. if (mustWait)
  40. node.waitForTake();
  41. else
  42. node.item = e;
  43. }
  44. }

Java 5的实现相对来说做了一些优化,只使用了一个锁,使用队列代替信号量也可以允许发布者直接发布数据,而不是要首先从阻塞在信号量处被唤醒。

3.4 Java 6实现

Java 6的SynchronousQueue的实现采用了一种性能更好的无锁算法 — 扩展的“Dual stack and Dual queue”算法。性能比Java5的实现有较大提升。竞争机制支持公平和非公平两种:非公平竞争模式使用的数据结构是后进先出栈(Lifo Stack);公平竞争模式则使用先进先出队列(Fifo Queue),性能上两者是相当的,一般情况下,Fifo通常可以支持更大的吞吐量,但Lifo可以更大程度的保持线程的本地化。

代码实现里的Dual Queue或Stack内部是用链表(LinkedList)来实现的,其节点状态为以下三种情况:

持有数据 – put()方法的元素

持有请求 – take()方法

这个算法的特点就是任何操作都可以根据节点的状态判断执行,而不需要用到锁。

其核心接口是Transfer,生产者的put或消费者的take都使用这个接口,根据第一个参数来区别是入列(栈)还是出列(栈)。

  1. /**
  2. * Shared internal API for dual stacks and queues.
  3. */
  4. static abstract class Transferer {
  5. /**
  6. * Performs a put or take.
  7. *
  8. * @param e if non-null, the item to be handed to a consumer;
  9. * if null, requests that transfer return an item
  10. * offered by producer.
  11. * @param timed if this operation should timeout
  12. * @param nanos the timeout, in nanoseconds
  13. * @return if non-null, the item provided or received; if null,
  14. * the operation failed due to timeout or interrupt --
  15. * the caller can distinguish which of these occurred
  16. * by checking Thread.interrupted.
  17. */
  18. abstract Object transfer(Object e, boolean timed, long nanos);
  19. }

TransferQueue实现如下(摘自Java 6源代码),入列和出列都基于Spin和CAS方法:

  1. /**
  2. * Puts or takes an item.
  3. */
  4. Object transfer(Object e, boolean timed, long nanos) {
  5. /* Basic algorithm is to loop trying to take either of
  6. * two actions:
  7. *
  8. * 1. If queue apparently empty or holding same-mode nodes,
  9. * try to add node to queue of waiters, wait to be
  10. * fulfilled (or cancelled) and return matching item.
  11. *
  12. * 2. If queue apparently contains waiting items, and this
  13. * call is of complementary mode, try to fulfill by CAS'ing
  14. * item field of waiting node and dequeuing it, and then
  15. * returning matching item.
  16. *
  17. * In each case, along the way, check for and try to help
  18. * advance head and tail on behalf of other stalled/slow
  19. * threads.
  20. *
  21. * The loop starts off with a null check guarding against
  22. * seeing uninitialized head or tail values. This never
  23. * happens in current SynchronousQueue, but could if
  24. * callers held non-volatile/final ref to the
  25. * transferer. The check is here anyway because it places
  26. * null checks at top of loop, which is usually faster
  27. * than having them implicitly interspersed.
  28. */
  29.  
  30. QNode s = null; // constructed/reused as needed
  31. boolean isData = (e != null);
  32.  
  33. for (;;) {
  34. QNode t = tail;
  35. QNode h = head;
  36. if (t == null || h == null) // saw uninitialized value
  37. continue; // spin
  38.  
  39. if (h == t || t.isData == isData) { // empty or same-mode
  40. QNode tn = t.next;
  41. if (t != tail) // inconsistent read
  42. continue;
  43. if (tn != null) { // lagging tail
  44. advanceTail(t, tn);
  45. continue;
  46. }
  47. if (timed &amp;&amp; nanos &lt;= 0) // can't wait
  48. return null;
  49. if (s == null)
  50. s = new QNode(e, isData);
  51. if (!t.casNext(null, s)) // failed to link in
  52. continue;
  53.  
  54. advanceTail(t, s); // swing tail and wait
  55. Object x = awaitFulfill(s, e, timed, nanos);
  56. if (x == s) { // wait was cancelled
  57. clean(t, s);
  58. return null;
  59. }
  60.  
  61. if (!s.isOffList()) { // not already unlinked
  62. advanceHead(t, s); // unlink if head
  63. if (x != null) // and forget fields
  64. s.item = s;
  65. s.waiter = null;
  66. }
  67. return (x != null)? x : e;
  68.  
  69. } else { // complementary-mode
  70. QNode m = h.next; // node to fulfill
  71. if (t != tail || m == null || h != head)
  72. continue; // inconsistent read
  73.  
  74. Object x = m.item;
  75. if (isData == (x != null) || // m already fulfilled
  76. x == m || // m cancelled
  77. !m.casItem(x, e)) { // lost CAS
  78. advanceHead(h, m); // dequeue and retry
  79. continue;
  80. }
  81.  
  82. advanceHead(h, m); // successfully fulfilled
  83. LockSupport.unpark(m.waiter);
  84. return (x != null)? x : e;
  85. }
  86. }
  87. }

Java并发包之同步队列SynchronousQueue理解的更多相关文章

  1. Java并发编程(您不知道的线程池操作), 最受欢迎的 8 位 Java 大师,Java并发包中的同步队列SynchronousQueue实现原理

    Java_并发编程培训 java并发程序设计教程 JUC Exchanger 一.概述 Exchanger 可以在对中对元素进行配对和交换的线程的同步点.每个线程将条目上的某个方法呈现给 exchan ...

  2. Java并发包——线程同步和锁

    Java并发包——线程同步和锁 摘要:本文主要学习了Java并发包里有关线程同步的类和锁的一些相关概念. 部分内容来自以下博客: https://www.cnblogs.com/dolphin0520 ...

  3. 并发编程-concurrent指南-阻塞队列-同步队列SynchronousQueue

    SynchronousQueue:同步Queue,属于线程安全的BlockingQueue的一种,此队列设计的理念类似于"单工模式",对于每个put/offer操作,必须等待一个t ...

  4. 【转载】阻塞队列之三:SynchronousQueue同步队列 阻塞算法的3种实现

    一.SynchronousQueue简介 Java 6的并发编程包中的SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作put必须等待消费者的移除 ...

  5. 阻塞队列之三:SynchronousQueue同步队列 阻塞算法的3种实现

    一.SynchronousQueue简介 Java 6的并发编程包中的SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作put必须等待消费者的移除 ...

  6. Java并发包

    刚看到一篇总结的比较全的JUC包总结,转载如下: 1. java.util.concurrent - Java 并发工具包 Java 5 添加了一个新的包到 Java 平台,java.util.con ...

  7. Java并发包源码学习系列:阻塞队列实现之SynchronousQueue源码解析

    目录 SynchronousQueue概述 使用案例 类图结构 put与take方法 void put(E e) E take() Transfer 公平模式TransferQueue QNode t ...

  8. Java并发包源码学习系列:CLH同步队列及同步资源获取与释放

    目录 本篇学习目标 CLH队列的结构 资源获取 入队Node addWaiter(Node mode) 不断尝试Node enq(final Node node) boolean acquireQue ...

  9. Java并发包源码学习系列:阻塞队列BlockingQueue及实现原理分析

    目录 本篇要点 什么是阻塞队列 阻塞队列提供的方法 阻塞队列的七种实现 TransferQueue和BlockingQueue的区别 1.ArrayBlockingQueue 2.LinkedBloc ...

随机推荐

  1. 某公司基于FineBI数据决策平台的试运行分析报告

    一.数据平台的软硬件环境 二.组织机构和权限体系 组织机构:平台中已集成一套组织机构,可以建立部门.人员.也可以与现有系统的组织机构集成,将组织机构导入到平台中. 功能权限:通过配置功能点URL的方式 ...

  2. CentOS 6.X启动流程

    CentOS 6.X启动流程 /boot分区 启动引导程序是默认可以识别boot分区的.因此在系统还无法加载硬盘的时候,boot分区是可以识别的! initramfs内存文件系统 CentOS 6.x ...

  3. SpriteBuilder中pivot关节中的Collide bodies属性

    在SpriteBuilder中,pivot类型的关节表示两个物体围绕一个中心旋转运动的关节,也称之为pin关节. 默认情况下Collide bodies是不选的.因为在大多数情况下你不希望pivot连 ...

  4. 2DSprite添加Light照射(Unity3D开发之十六)

    猴子原创,欢迎转载.转载请注明: 转载自Cocos2Der-CSDN,谢谢! 原文地址: http://blog.csdn.net/cocos2der/article/details/45534245 ...

  5. Java泛型type体系

    最近看开源代码,看到里面很多Java泛型,并且通过反射去获取泛型信息.如果说要看懂泛型代码,那还是比较容易,但是如果要自己利用泛型写成漂亮巧妙的框架,那必须对泛型有足够的了解.所以这两三天就不在不断地 ...

  6. unix设计哲学

    说到Unix为我们所带来的软件开发的哲学,我必需要说一说.Unix遵循的原则是KISS(Keep it simple, stupid).在http://en.wikipedia.org/wiki/Un ...

  7. c# http请求ajax页面

    我们在用Http请求的时候,某些页面是ajax加载的,所以请求过来的页面数据不完整.也就是说ajax局部加载数据的地方,我们请求不到,这时候该怎么办呢? WebDriver+phantomjs 这两个 ...

  8. MVC3 项目总结

    验证 Validation 多样化验证规则 http://www.cnblogs.com/xling/archive/2012/07/11/2587002.html 最常见的验证方式是:在实体的属性上 ...

  9. 轻松解决oracle11g 空表不能exp导出的问题

    轻松解决oracle11g 空表不能exp导出的问题 [引用 2012-9-22 18:06:36]     字号:大 中 小 oracle11g的新特性,数据条数是0时不分配segment,所以就不 ...

  10. 论MVC中的传值

    2个页面分别为Father.cshtml.Child.cshtml 2个控制器分别为FatherController.cs.ChildController.cs 1个js,为Father.js 一.F ...