《java.util.concurrent 包源码阅读》16 一种特别的BlockingQueue:SynchronousQueue
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的更多相关文章
- 《java.util.concurrent 包源码阅读》 结束语
<java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...
- 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分
这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...
- 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包
Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...
- 《java.util.concurrent 包源码阅读》04 ConcurrentMap
Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...
- 《java.util.concurrent 包源码阅读》17 信号量 Semaphore
学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...
- 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue
对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...
- 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇
concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...
- 《java.util.concurrent 包源码阅读》24 Fork/Join框架之Work-Stealing
仔细看了Doug Lea的那篇文章:A Java Fork/Join Framework 中关于Work-Stealing的部分,下面列出该算法的要点(基本是原文的翻译): 1. 每个Worker线程 ...
- 《java.util.concurrent 包源码阅读》27 Phaser 第一部分
Phaser是JDK7新添加的线程同步辅助类,作用同CyclicBarrier,CountDownLatch类似,但是使用起来更加灵活: 1. Parties是动态的. 2. Phaser支持树状结构 ...
随机推荐
- C#委托
关于什么是委托,委托如何使用,我在这里就不说了. 需要说的: 委托是函数指针链 委托的 BeginInvoke 委托如果出现异常,会如何 如果不知道函数指针,可以继续往下看,我来告诉大家,为何需要委托 ...
- 记一次vscode升级后,格式化Vue出现的问题
一.VSCode中使用vetur插件格式化vue文件时,stylus代码会自动加上大括号.冒号和分号 本来就是简写比较方便舒服,结果一个格式化回到十年前 解决方案: vscode 文件 ->首 ...
- 翻译:MLAPP(2.3节 一些常见的离散分布)
笔者:尝试翻译MLAPP(Machine Learning: a Probabilistic Perspective)一书,供机器学习的学者参考,如有错误理解之处请指出,不胜感激!(如需转载,请联系本 ...
- ELK系列~对fluentd参数的理解
这段时候一直在研究ELK框架,主要集成在对fluentd和nxlog的研究上,国内文章不多,主要看了一下官方的API,配合自己的理解,总结了一下,希望可以帮到刚入行的朋友们! Fluentd(日志收集 ...
- 基于vip和twemproxy代理实现redis集群的无感知弹性扩容
目标是实现redis集群的无感知弹性扩容 关键点 1是无感知,即对redis集群的用户来说服务ip和port保持不变 2.弹性扩容,指的是在需要时刻可以按照业务扩大redis存储容量. 最原始的twe ...
- JavaScript数组去重方法汇总
1.运用数组的特性 1.遍历数组,也遍历辅助数组,找出两个数组中是否有相同的项,若有则break,没有的话就push进去. //第一版本数组去重 function unique(arr){ var r ...
- 《mysql必知必会》读书笔记--存储过程的使用
以前对mysql的认识与应用只是停留在增删改查的阶段,最近正好在学习mysql相关内容,看了一本书叫做<MySQL必知必会>,看了之后对MySQL的高级用法有了一定的了解.以下内容只当读书 ...
- 在找一份相对完整的Webpack项目配置指南么?这里有
Webpack已经出来很久了,相关的文章也有很多,然而比较完整的例子却不是很多,让很多新手不知如何下脚,下脚了又遍地坑 说实话,官方文档是蛮乱的,而且有些还是错的错的..很多配置问题只有爬过坑才知道 ...
- GCD(欧拉函数)
GCD Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submissio ...
- Windows下caffe的配置和调用caffe库(一)
一.Windows下caffe的配置: 1. 下载caffe官网提供的开发包,https://github.com/microsoft/caffe 2. 将caffe-master目录下的Window ...