《java.util.concurrent 包源码阅读》28 Phaser 第二部分
这一部分来分析Phaser关于线程等待的实现。所谓线程等待Phaser的当前phase结束并转到下一个phase的过程。Phaser提供了三个方法:
// 不可中断,没有超时的版本
public int awaitAdvance(int phase); // 可以中断,没有超时的版本
public int awaitAdvanceInterruptibly(int phase); // 可以中断,带有超时的版本
public int awaitAdvanceInterruptibly(int phase, long timeout, TimeUnit unit);
这三个版本的方法的实现大体类似,区别在于第二个版本多了中断异常,第三个版本多了中断异常和超时异常。
public int awaitAdvance(int phase) {
// 获取当前state
final Phaser root = this.root;
long s = (root == this) ? state : reconcileState();
int p = (int)(s >>> PHASE_SHIFT);
// 检查给定的phase是否和当前的phase一直
if (phase < 0)
return phase;
if (p == phase)
return root.internalAwaitAdvance(phase, null);
return p;
}
// 多了一个对于中断的检查然后抛出中断异常
public int awaitAdvanceInterruptibly(int phase)
throws InterruptedException {
final Phaser root = this.root;
long s = (root == this) ? state : reconcileState();
int p = (int)(s >>> PHASE_SHIFT);
if (phase < 0)
return phase;
if (p == phase) {
// 使用QNode实现中断和超时,这里不带超时
QNode node = new QNode(this, phase, true, false, 0L);
p = root.internalAwaitAdvance(phase, node);
// 对于中断的情况,抛出中断异常
if (node.wasInterrupted)
throw new InterruptedException();
}
return p;
}
// 多了中断异常和超时异常
public int awaitAdvanceInterruptibly(int phase,
long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException {
long nanos = unit.toNanos(timeout);
final Phaser root = this.root;
long s = (root == this) ? state : reconcileState();
int p = (int)(s >>> PHASE_SHIFT);
if (phase < 0)
return phase;
if (p == phase) {
QNode node = new QNode(this, phase, true, true, nanos);
p = root.internalAwaitAdvance(phase, node);
// 中断异常
if (node.wasInterrupted)
throw new InterruptedException();
// 没有进入下一个phase,抛出超时异常
else if (p == phase)
throw new TimeoutException();
}
return p;
}
上述三个方法都是调用了internalAwaitAdvance方法来实现等待,因此来看internalAwaitAdvance方法:
private int internalAwaitAdvance(int phase, QNode node) {
// 释放上一个phase的资源
releaseWaiters(phase-1);
// node是否被加入到队列中
boolean queued = false;
// 记录前一个Unarrived,用来增加spin值
int lastUnarrived = 0;
int spins = SPINS_PER_ARRIVAL;
long s;
int p;
// 循环操作直到phase值发生了变化
while ((p = (int)((s = state) >>> PHASE_SHIFT)) == phase) {
// 不可中断的模式,使用自旋等待
if (node == null) {
int unarrived = (int)s & UNARRIVED_MASK;
if (unarrived != lastUnarrived &&
(lastUnarrived = unarrived) < NCPU)
spins += SPINS_PER_ARRIVAL;
boolean interrupted = Thread.interrupted();
// 发生了中断时,使用一个node来记录这个中断
if (interrupted || --spins < 0) {
node = new QNode(this, phase, false, false, 0L);
node.wasInterrupted = interrupted;
}
}
// 当前线程的node可以结束等待了,后面会分析isReleasible方法
else if (node.isReleasable())
break;
// 把node加入到队列中
else if (!queued) {
// 根据phase值不同,使用不同的队列
AtomicReference<QNode> head = (phase & 1) == 0 ? evenQ : oddQ;
QNode q = node.next = head.get();
// 检查队列的phase是否和要求的phase一致并且Phaser的phase没有发生变化
// 符合这两个条件才把node添加到队列中去
if ((q == null || q.phase == phase) &&
(int)(state >>> PHASE_SHIFT) == phase)
queued = head.compareAndSet(q, node);
}
// node加入队列后直接等待
else {
try {
// 对于普通线程来说,这个方法作用就是循环直到isReleasable返回true
// 或者block方法返回true
ForkJoinPool.managedBlock(node);
} catch (InterruptedException ie) {
node.wasInterrupted = true;
}
}
}
// 对于进入队列的node,重置一些属性
if (node != null) {
// 释放thread,不要再使用unpark
if (node.thread != null)
node.thread = null;
// 对于不可中断模式下发生的中断,清除中断状态
if (node.wasInterrupted && !node.interruptible)
Thread.currentThread().interrupt();
// phase依旧没有变化表明同步过程被终止了
if (p == phase && (p = (int)(state >>> PHASE_SHIFT)) == phase)
return abortWait(phase);
}
// 通知所有的等待线程
releaseWaiters(phase);
return p;
}
下面来看QNode,它实现了ManagedBlocker接口(见ForkJoinPool),ManagedBlocker包含两个方法:isReleasable和block。
isReleasable表示等待可以结束了,下面是QNode实现的isReleasable:
public boolean isReleasable() {
// 没了等待线程,通常会在外部使用"node.thread = null"来释放等待线程,这时可以结束等待
if (thread == null)
return true;
// phase发生变化,可以结束等待
if (phaser.getPhase() != phase) {
thread = null;
return true;
}
// 可中断的情况下发生线程中断,可以结束等待
if (Thread.interrupted())
wasInterrupted = true;
if (wasInterrupted && interruptible) {
thread = null;
return true;
}
// 设置超时的情况下,发生超时,可以结束等待
if (timed) {
if (nanos > 0L) {
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
}
if (nanos <= 0L) {
thread = null;
return true;
}
}
return false;
}
最后来看QNode实现的block方法,核心思想是用LockSupport来实现线程等待:
public boolean block() {
if (isReleasable())
return true;
// 没有设置超时的情况
else if (!timed)
LockSupport.park(this);
// 设置超时的情况
else if (nanos > 0)
LockSupport.parkNanos(this, nanos);
return isReleasable();
}
最后来看releaseWaiters方法,看看怎么释放node队列:
private void releaseWaiters(int phase) {
QNode q;
Thread t;
AtomicReference<QNode> head = (phase & 1) == 0 ? evenQ : oddQ;
// 如果phase已经发生了变化,才能释放
while ((q = head.get()) != null &&
q.phase != (int)(root.state >>> PHASE_SHIFT)) {
// 释放节点并转到下一个节点
if (head.compareAndSet(q, q.next) &&
(t = q.thread) != null) {
// 释放线程
q.thread = null;
// 通知线程结束等待
LockSupport.unpark(t);
}
}
}
到这里就把Phaser分析完了。
《java.util.concurrent 包源码阅读》28 Phaser 第二部分的更多相关文章
- 《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 包源码阅读》05 BlockingQueue
想必大家都很熟悉生产者-消费者队列,生产者负责添加元素到队列,如果队列已满则会进入阻塞状态直到有消费者拿走元素.相反,消费者负责从队列中拿走元素,如果队列为空则会进入阻塞状态直到有生产者添加元素到队列 ...
- 《java.util.concurrent 包源码阅读》10 线程池系列之AbstractExecutorService
AbstractExecutorService对ExecutorService的执行任务类型的方法提供了一个默认实现.这些方法包括submit,invokeAny和InvokeAll. 注意的是来自E ...
随机推荐
- 二:Linux 的基本命令、VI编辑器、Linux中软件安装
Linux 的基本命令 1. 文件操作 a) Windows 是多根的文件系统,物理上是 1 到多块硬盘,逻辑上分为 C.D.E--盘, 每个盘都是一棵树.Linux 是单根的文件系统,不分 CDE ...
- stringstream缓存正确清除方法
当需要清空stringsteam缓存的时候,到底是.str("")呢还是.clear(); 实际上,我认为,保守起见,两者都需要 clear() 是清除 state flag st ...
- WordPress 4.8 安装配置教程 (基于 centos 7.3, php 7.0, mysql 5.7.19, nginx 1.12.1)
最近想要整个 blog,记录自己工作.学习中的点滴.Wordpress 自然是首选,因为内容才是关键,所以也就不怕别人说太 low.网上大部份都是讲 wordpress 配合 apache 的安装教程 ...
- JavaBean编辑器的简单介绍
引言 Sun所指定的JavaBean规范很大程度上是为IDE准备的--它让IDE能够以可视化的方式设置JavaBean的属性.如果在IDE中开发一个可视化的应用程序,则需要通过属性设置的方式对组成应用 ...
- vue-cli + sass 的正确打开方式
关于在vue-cli搭建的项目中怎么配置sass,网上搜到的基本是这种答案: 但是我认为,直接将样式写在每个单文件的<style>里,是十分不明智的做法.且不说node-sass安装过程的 ...
- MAVEN 打包JAR
<build> <finalName>edu-service-user</finalName> <resources> <resource> ...
- [ACdream]女神教你字符串——违和感
题目描述: 女神最喜欢字符串了,字符串神马的最有爱了. 女神是一个重度强迫症患者,面对不是对称的东西,她会觉得太违和了,就会爆炸.所以她手上的字符串都是回文的,像什么a,b,aabaa,abcba,上 ...
- 242. Valid Anagram(leetcode)
Given two strings s and t, write a function to determine if t is an anagram of s. For example, s = & ...
- GCC(警告.优化以及调试选项)
GCC(警告.优化以及调试选项) [介绍] gcc and g++分别是gnu的c & c++编译器 gcc/g++在执行编译工作的时候,总共需要4步 1.预处理,生成.i的文件 预处 ...
- poj 2486 Apple Tree(树形DP 状态方程有点难想)
Apple Tree Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 9808 Accepted: 3260 Descri ...