《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支持树状结构 ...
随机推荐
- JavaScript函数之实际参数对象(arguments) / callee属性 / caller属性 / 递归调用 / 获取函数名称的方法
函数的作用域:调用对象 JavaScript中函数的主体是在局部作用域中执行的,该作用域不同于全局作用域.这个新的作用域是通过将调用对象添加到作用域链的头部而创建的(没怎么理解这句话,有理解的亲可以留 ...
- 3des用法示例,已测试
using System;using System.Data;using System.Configuration;using System.Web;using System.Web.Security ...
- java zip解压
/** * 解压文件到指定目录 * @param zipFile * @param descDir * @author sqdll */@SuppressWarnings("rawtypes ...
- 超级详细 一听就会:利用JavaScript jQuery实现图片无限循环轮播(不借助于轮播插件)
前言 作为一个前端工程师,无论公司是什么行业,无论你做什么端,基本都会遇到一个避不开的动画效果:循环轮播.做轮播并不难,市场上的轮播插件有很多,其中比较著名的是swiper,使用也非常简单.但轮播插件 ...
- 使用速卖通开放平台云API调用菜鸟组件实现云打印
公司是跨境电商,使用速卖通平台卖玩具,我们自己研发的ERP是基于速卖通开放平台API,实现订单的发货提交,打印面单等功能 近期公司要求使用菜鸟组件云打印,去平台里看下,有这个API,如下图所示 实现也 ...
- R学习笔记 第五篇:数据变换和清理
在使用R的分组操作之前,首先要了解R语言包,包实质上是实现特定功能的,预先写好的代码库(library),R拥有大量的软件包,许多包都是由某一领域的专家编写的,但并不是所有的包都有很高的质量的,在使用 ...
- Pie
Problem Description My birthday is coming up and traditionally I'm serving pie. Not just one pie, no ...
- mysql启动时报错:Starting MySQL... ERROR! The server quit without updating PID file (/opt/mysql/data/mysql.pid) 的解决方法
出现问题的可能性 1.可能是/opt/mysql/data/数据目录mysql用户没有权限(修改数据目录的权限) 解决方法 :给予权限,执行 "chown -R mysql.mysql / ...
- centos 下安装jdk、tomcat 以及tomcat无法从外部访问的解决办法
centos 下安装jdk.tomcat 以及tomcat无法从外部访问的解决办法 原创 2014年08月28日 10:24:33 标签: selinux enforce cent 2223 昨天在c ...
- Java一点输入输出技巧
输入: 格式1:Scanner sc = new Scanner(System.in); 格式2:Scanner sc = new Scanner(new BufferedInputStream(Sy ...