SynchronousQueue是一种很特别的BlockingQueue,任何一个添加元素的操作都必须等到另外一个线程拿走元素才会结束。也就是SynchronousQueue本身不会存储任何元素,相当于生产者和消费者手递手直接交易。

SynchronousQueue有一个fair选项,如果fair为true,称为fair模式,否则就是unfair模式。

在fair模式下,所有等待的生产者线程或者消费者线程会按照开始等待时间依次排队,然后按照等待先后顺序进行匹配交易。这种情况用队列实现。

在unfair模式下,则刚好相反,后来先匹配,这种情况用栈实现。

     */
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue() : new TransferStack();
}

因为添加元素和拿走元素是类似手递手交易的,所以对于拿走元素和添加元素操作,SynchronousQueue调用的是Transferer同一个方法transfer。

当object为null时表示是拿走元素,用于消费者线程,否则则是添加元素,用于生产者线程。因此transfer方法是分析的重点。

abstract Object transfer(Object e, boolean timed, long nanos);

首先来看用于fair模式的TransferQueue的transfer方法:

看代码之前,来理一下逻辑:

1. 开始队列肯定是空。

2. 线程进入队列,如果队列是空的,那么就添加该线程进入队列,然后进行等待(要么有匹配线程出现,要么就是该请求超时取消)

3. 第二个线程进入,如果前面一个线程跟它属于不同类型,也就是说两者是可以匹配的,那么就从队列删除第一个线程。

如果是相同的线程,那么做法参照2。

理清了基本逻辑,也就是会有两种情况:

1. 队列为空或者队列中的等待线程是相同类型

2. 队列中的等待线程是匹配的类型

        Object transfer(Object e, boolean timed, long nanos) {

            QNode s = null;
// e不是null表示是生成者线程,e就是产品,反之就是消费者线程
boolean isData = (e != null); for (;;) {
QNode t = tail;
QNode h = head;
// tail和head在队列创建时会被初始化成一个虚拟节点
// 因此发现没有初始化,重新循环等待直到初始化完成
if (t == null || h == null)
continue; // 队列为空或等待线程类型相同(不同类型才能匹配)
// 这两种情况都要把当前线程加入到等待队列中
if (h == t || t.isData == isData) {
QNode tn = t.next;
// tail对象已经被更新,出现不一致读的现象,重新循环
if (t != tail)
continue;
// 添加线程到等待队列时会先更新当前tail的next,然后
// 更新tail本身,因此出现只有next被更新的情况,应该
// 更新tail,然后重新循环
if (tn != null) {
advanceTail(t, tn);
continue;
}
// 设定了超时,剩余等待时间耗尽的时候,就无需再等待
if (timed && nanos <= 0)
return null;
// 首次使用s的时候,新建一个节点保存当前线程和数据来初始化s
if (s == null)
s = new QNode(e, isData);
// 尝试更新tail的next,把新建节点添加到tail的后面,如果失败了,就重新循环
if (!t.casNext(null, s))
continue;
// 把新建的节点设置为tail
advanceTail(t, s);
// 等待匹配线程,成功匹配则返回的匹配的值
// 否则返回当前节点,因此s和x相同表示请求被取消
Object x = awaitFulfill(s, e, timed, nanos);
if (x == s) {
clean(t, s);
return null;
} // 这个时候已经匹配成功了,s应该是排在第一个的等待线程
// 如果s依然在队列中,那么需要更新head。
// 更新head的方法是把s这个排在第一位的节点作为新的head
// 因此需要重置一些属性使它变成虚拟节点
if (!s.isOffList()) {
advanceHead(t, s);
if (x != null)
s.item = s;
s.waiter = null;
}
// x不为null表示拿到匹配线程的数据(消费者拿到生产者的数据),
// 因此返回该数据,否则返回本身的数据(生成者返回自己的数据)
return (x != null) ? x : e; } else { // 线程可以匹配
// 因为是队列,因此匹配的是第一个节点
QNode m = h.next;
// 同样需要检查不一致读的情况
if (t != tail || m == null || h != head)
continue; Object x = m.item;
// 匹配失败时,把m从队列中移走,重新循环
if (isData == (x != null) || // m已经被匹配了
x == m || // m已经被取消了
!m.casItem(x, e)) { // 用CAS设置m的数据为null
advanceHead(h, m);
continue;
} // 匹配成功,更新head
advanceHead(h, m);
// 解除m的线程等待状态
LockSupport.unpark(m.waiter);
// 返回匹配的数据
return (x != null) ? x : e;
}
}
}

接着来用于Unfair模式的TransferStack的transfer方法

大体逻辑应该是一样的,不同就是队列的入队和出队操作对应到栈时就是入栈和出栈的操作。

        Object transfer(Object e, boolean timed, long nanos) {
SNode s = null;
int mode = (e == null) ? REQUEST : DATA; for (;;) {
SNode h = head;
// 栈为空或者节点类型相同的情况
if (h == null || h.mode == mode) {
if (timed && nanos <= 0) {
// 检查栈顶节点是否已经取消,如果已经取消,弹出节点
// 重新循环,接着检查新的栈顶节点
if (h != null && h.isCancelled())
casHead(h, h.next);
else
return null;
// 新建节点,并且尝试把新节点入栈
} else if (casHead(h, s = snode(s, e, h, mode))) {
// 等待匹配,如果发现是被取消的情况,则释放节点,返回null
SNode m = awaitFulfill(s, timed, nanos);
if (m == s) {
clean(s);
return null;
}
// 如果匹配的成功两个节点是栈顶的两个节点
// 把这两个节点都弹出
if ((h = head) != null && h.next == s)
casHead(h, s.next); // help s's fulfiller
return (mode == REQUEST) ? m.item : s.item;
}
} else if (!isFulfilling(h.mode)) { // 栈顶节点没有和其他线程在匹配,可以匹配
if (h.isCancelled()) // 栈顶节点的请求已经被取消
casHead(h, h.next); // 移除栈顶元素重新循环
// 尝试把该节点也入栈,该节点设置为正在匹配的状态
// 也就是isFulfilling返回true
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
for (;;) {
// 栈顶节点(当前线程的节点)和它的下一个节点进行匹配,m为null意味着
// 栈里没有其他节点了,因为前面该节点入栈了,需要弹出这个节点重新循环
SNode m = s.next;
if (m == null) {
casHead(s, null);
s = null;
break;
} // 这个时候是有节点可以匹配的,尝试为这两个节点做匹配
SNode mn = m.next;
// m和s匹配成功,弹出这两个节点,返回数据;匹配失败,把m移除
if (m.tryMatch(s)) {
casHead(s, mn);
return (mode == REQUEST) ? m.item : s.item;
} else
s.casNext(m, mn);
}
}
// 栈顶正在匹配,参见代码:
// else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
// 做法基本类似,只是这里帮助其他线程匹配,无论成功与否
// 都要重新循环
} else {
SNode m = h.next;
if (m == null)
casHead(h, null);
else {
SNode mn = m.next;
if (m.tryMatch(h))
casHead(h, mn);
else
h.casNext(m, mn);
}
}
}
}

TransferQueue和TransferStack的算法实现可以参考 这里

《java.util.concurrent 包源码阅读》16 一种特别的BlockingQueue:SynchronousQueue的更多相关文章

  1. 《java.util.concurrent 包源码阅读》 结束语

    <java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...

  2. 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

    这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...

  3. 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包

    Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...

  4. 《java.util.concurrent 包源码阅读》04 ConcurrentMap

    Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...

  5. 《java.util.concurrent 包源码阅读》17 信号量 Semaphore

    学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...

  6. 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue

    对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...

  7. 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇

    concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...

  8. 《java.util.concurrent 包源码阅读》24 Fork/Join框架之Work-Stealing

    仔细看了Doug Lea的那篇文章:A Java Fork/Join Framework 中关于Work-Stealing的部分,下面列出该算法的要点(基本是原文的翻译): 1. 每个Worker线程 ...

  9. 《java.util.concurrent 包源码阅读》27 Phaser 第一部分

    Phaser是JDK7新添加的线程同步辅助类,作用同CyclicBarrier,CountDownLatch类似,但是使用起来更加灵活: 1. Parties是动态的. 2. Phaser支持树状结构 ...

随机推荐

  1. 使用acs-engine在Azure中国区部署kubernetes集群详解

    转载请注明出处:http://www.cnblogs.com/wayneiscoming/p/7649642.html 1. acs-engine简介 ACS是微软在2015年12月推出的一项基于容器 ...

  2. mb_substr函数

    定义和用法 mb_substr() 截取字符串中指定长度字符 注:常用于中文截取,可以避免截取时候出现乱码,即截取半个字符的情况. 类似函数 substr(),iconv_substr() 语法 mb ...

  3. BZOJ-1045-[HAOI2008] 糖果传递(中位数原理)

    Description 有n个小朋友坐成一圈,每人有ai个糖果.每人只能给左右两人传递糖果.每人每次传递一个糖果代价为1. Input 第一行一个正整数nn<=1'000'000,表示小朋友的个 ...

  4. LeetCode 33. Search in Rotated Sorted Array(在旋转有序序列中搜索)

    Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e. ...

  5. 【20171025早】alert(1) to win 练习

    本人黑绝楼,自称老黑,男,25岁,曾经在BAT工作过两年,但是一直都是底层人员,整天做重复性工作,甚敢无趣,曾和工作十年之久的同事聊天,发现对方回首过往,生活是寡淡如水,只有机械性工作.旋即老黑毅然决 ...

  6. hibernate的session详解

  7. iOS之 git 简单使用

    之前没有用过git管理代码, 现在 公司要求使用git,咱 就 记记 今天所学的东西.以后也好方便查阅 和补充. 首先 我描述一下我今天提交代码的情况. 同伴给了我一个 从git库下来的2个项目的包. ...

  8. java 操作hbase1.2

    说明: .第一部分为代码 .第二部分为工程pom文件 [java] view plain copy import org.apache.hadoop.conf.Configuration; impor ...

  9. Codeforces 376C. Socks

    C. Socks time limit per test 2 seconds memory limit per test 256 megabytes input standard input outp ...

  10. win10提示管理员已阻止你运行此应用,如何强制运行

      方法/步骤     这里以自己遇到的一个软件为例,下图就是禁止的图画:   在开始菜单中输入"cmd",然后以管理员身份运行:   按住"shift"键,同 ...