Phaser是JDK7新添加的线程同步辅助类,作用同CyclicBarrier,CountDownLatch类似,但是使用起来更加灵活:

1. Parties是动态的。

2. Phaser支持树状结构,即Phaser可以有一个父Phaser。

Phaser的构造函数涉及到两个参数:父Phaser和初始的parties,因此提供了4个构造函数:

public Phaser();
public Phaser(int parties); public Phaser(Phaser parent);
public Phaser(Phaser parent, int parties);

因为Phaser的特色在在于动态的parties,因此首先来看动态更新parties是如何实现的。

Phaser提供了两个方法:register和bulkRegister,前者会添加一个需要同步的线程,后者会添加parties个需要同步的线程。

    public int register() {
return doRegister(1);
} // 增加了参数的检查
public int bulkRegister(int parties) {
if (parties < 0)
throw new IllegalArgumentException();
if (parties == 0)
return getPhase();
return doRegister(parties);
}

两个方法都调用了doRegister方法,因此接下来就来看看doRegister方法。

在分析doRegister之前先来说说Phaser的成员变量:state,它存储了Phaser的状态信息:

private volatile long state;

1. state的最高位是一个标志位,1表示Phaser的线程同步已经结束,0表示线程同步正在进行

2. state的低32位中,低16位表示没有到达的线程数量,高16位表示Parties值

3. state的高32位除了最高位之外的其他31位表示的Phaser的phase,可以理解为第多少次同步(从0开始计算)。

介绍完了state,来看方法doRegister:

    private int doRegister(int registrations) {
// 把registrations值同时加到parties值和还未达到的线程数量中去
long adj = ((long)registrations << PARTIES_SHIFT) | registrations;
final Phaser parent = this.parent;
int phase;
for (;;) {
long s = state;
int counts = (int)s;
int parties = counts >>> PARTIES_SHIFT;
int unarrived = counts & UNARRIVED_MASK;
// 超过了允许的最大parties
if (registrations > MAX_PARTIES - parties)
throw new IllegalStateException(badRegister(s));
// 最高位为1,表示Phaser的线程同步已经结束
else if ((phase = (int)(s >>> PHASE_SHIFT)) < 0)
break;
// Phaser中的parties不是0
else if (counts != EMPTY) {
// 如果当前Phaser没有父Phaser,或者如果有父Phaser,
// 刷新自己的state值,如果刷新后的state没有变化。
// 这里刷新子Phaser的原因在于,会出现父Phaser已经进入下一个phase
// 而子Phaser却没有及时进入下一个phase的延迟现象
if (parent == null || reconcileState() == s) {
// 如果所有线程都到达了,等待Phaser进入下一次同步开始
if (unarrived == 0)
root.internalAwaitAdvance(phase, null);
// 更新state成功,跳出循环完成注册
else if (UNSAFE.compareAndSwapLong(this, stateOffset,
s, s + adj))
break;
}
}
// 第一次注册,且不是子Phaser
else if (parent == null) {
// 更新当前Phaser的state值成功则完成注册
long next = ((long)phase << PHASE_SHIFT) | adj;
if (UNSAFE.compareAndSwapLong(this, stateOffset, s, next))
break;
}
// 第一次注册到子Phaser
else {
// 锁定当前Phaser对象
synchronized (this) {
// 再次检查state值,确保没有被更新
if (state == s) {
// 注册到父Phaser中去
parent.doRegister(1);
do { // 获取当前phase值
phase = (int)(root.state >>> PHASE_SHIFT);
} while (!UNSAFE.compareAndSwapLong
(this, stateOffset, state,
((long)phase << PHASE_SHIFT) | adj));// 更新当前Phaser的state值
break;
}
}
}
}
return phase;
}

看完了注册,那么来看同步操作的arrive,这里也涉及到两个方法:arrive和arriveAndDeregister,前者会等待其他线程的到达,后者则会立刻返回:

    public int arrive() {
return doArrive(false);
} public int arriveAndDeregister() {
return doArrive(true);
}

两个方法都调用了doArrive方法,区别在于参数一个是false,一个是true。那么来看doArrive:

    private int doArrive(boolean deregister) {
// arrive需要把未到达的线程数减去1,
// deregister为true,需要把parties值也减去1
int adj = deregister ? ONE_ARRIVAL|ONE_PARTY : ONE_ARRIVAL;
final Phaser root = this.root;
for (;;) {
// 如果是有父Phaser,首先刷新自己的state
long s = (root == this) ? state : reconcileState();
int phase = (int)(s >>> PHASE_SHIFT);
int counts = (int)s;
int unarrived = (counts & UNARRIVED_MASK) - 1;
// 最高位为1,表示同步已经结束,返回phase值
if (phase < 0)
return phase;
// 如果parties为0或者在此次arrive之前所有线程到达
else if (counts == EMPTY || unarrived < 0) {
// 对于非子Phaser来说,上述情况的arrive肯定是非法的
// 对于子Phaser首先刷新一下状态再做检查
if (root == this || reconcileState() == s)
throw new IllegalStateException(badArrive(s));
}
// 正常情况下,首先更新state
else if (UNSAFE.compareAndSwapLong(this, stateOffset, s, s-=adj)) {
// 所有线程都已经到达
if (unarrived == 0) {
// 计算parties作为下一个phase的未到达的parties
long n = s & PARTIES_MASK;
int nextUnarrived = (int)n >>> PARTIES_SHIFT;
// 调用父Phaser的doArrive
if (root != this)
// 如果下一个phase的未到达的parties为0,则需要向
// 父Phaser取消注册
return parent.doArrive(nextUnarrived == 0);
// 正在进入下一个Phase,默认的实现是nextUnarrived为0
// 表示正在进入下一个Phase,因为下一个phase的parties
// 为0,需要等待parties不为0
if (onAdvance(phase, nextUnarrived))
// 正在等待下一个phase,设置状态为终止
n |= TERMINATION_BIT;
else if (nextUnarrived == 0)
// 下一个phase的parties为0,更新未到达的parties的值
n |= EMPTY;
else
// 更新下一个phase的未到达的parties的值
n |= nextUnarrived;
// phase值加1
n |= (long)((phase + 1) & MAX_PHASE) << PHASE_SHIFT; // 更新state值
UNSAFE.compareAndSwapLong(this, stateOffset, s, n); // 唤醒等待的线程
releaseWaiters(phase);
}
return phase;
}
}
}

关于arrive还有一个方法:arriveAndAwaitAdvance。这个方法会等到下一个phase开始再返回,相等于doArrive方法添加了awaitAdvance方法的功能。基本逻辑和上面说的doArrive方法类似:

    public int arriveAndAwaitAdvance() {
final Phaser root = this.root;
for (;;) {
long s = (root == this) ? state : reconcileState();
int phase = (int)(s >>> PHASE_SHIFT);
int counts = (int)s;
int unarrived = (counts & UNARRIVED_MASK) - 1;
if (phase < 0)
return phase;
else if (counts == EMPTY || unarrived < 0) {
// 对于非子Phaser来说,因为可以等待下一个phase,
// 所以不是非法arrive
if (reconcileState() == s)
throw new IllegalStateException(badArrive(s));
}
else if (UNSAFE.compareAndSwapLong(this, stateOffset, s,
s -= ONE_ARRIVAL)) {
// 还有其他线程没有达到,就会等待直到下一个phase开始
if (unarrived != 0)
return root.internalAwaitAdvance(phase, null);
if (root != this)
return parent.arriveAndAwaitAdvance();
long n = s & PARTIES_MASK; // base of next state
int nextUnarrived = (int)n >>> PARTIES_SHIFT;
if (onAdvance(phase, nextUnarrived))
n |= TERMINATION_BIT;
else if (nextUnarrived == 0)
n |= EMPTY;
else
n |= nextUnarrived;
int nextPhase = (phase + 1) & MAX_PHASE;
n |= (long)nextPhase << PHASE_SHIFT;
if (!UNSAFE.compareAndSwapLong(this, stateOffset, s, n))
return (int)(state >>> PHASE_SHIFT);
releaseWaiters(phase);
return nextPhase;
}
}
}

这一部分主要讲了Phaser的动态更新parties以及线程的arrive,下一部分将会分析线程等待的实现。

《java.util.concurrent 包源码阅读》27 Phaser 第一部分的更多相关文章

  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 包源码阅读》05 BlockingQueue

    想必大家都很熟悉生产者-消费者队列,生产者负责添加元素到队列,如果队列已满则会进入阻塞状态直到有消费者拿走元素.相反,消费者负责从队列中拿走元素,如果队列为空则会进入阻塞状态直到有生产者添加元素到队列 ...

  9. 《java.util.concurrent 包源码阅读》10 线程池系列之AbstractExecutorService

    AbstractExecutorService对ExecutorService的执行任务类型的方法提供了一个默认实现.这些方法包括submit,invokeAny和InvokeAll. 注意的是来自E ...

随机推荐

  1. C语言第一次实验报告————PTA实验1.2.3内容

    一.PTA实验作业 题目1.温度转换 本题要求编写程序,计算华氏温度100°F对应的摄氏温度.计算公式:C=5×(F−32)/9,式中:C表示摄氏温度,F表示华氏温度,输出数据要求为整型. 1.实验代 ...

  2. win10 uwp 存放网络图片到本地

    有时候我们的网络很垃圾,我的的UWP要在第一次打开网络图片,就把图片存放到本地,下次可以从本地打开. 有时候用户使用的是流量网络,不能每次都联网下载. 我们不得在应用存放用户打开的图片. 这就是先把图 ...

  3. ubuntu下MySQL修改root密码的多种方法,phpmyadmin空密码无法登陆的解决方法

    phpmyadmin是默认不允许使用空密码的,所以若是在安装时没有设置密码,在登陆phpmyadmin时是个很头疼的问题 方法1是修改phpmyadmin的配置文件,这里不做推荐.. 方法2: php ...

  4. ThreadPoolExecutor系列<一、ThreadPoolExecutor 机制>

    本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681529.html 解决问题: 1. 处理大量异步任务时能减少每 ...

  5. 基于FFMpeg的C#录屏全攻略

    最近负责一个录屏的小项目,需要录制Windows窗口内容并压缩保存到指定文件夹,本想使用已有的录屏软件,但是本着学习的态度去探索了FFMpeg,本文主要介绍基于FFMpeg开源项目的C#录屏软件开发. ...

  6. Akka(30): Http:High-Level-Api,Routing DSL

    在上篇我们介绍了Akka-http Low-Level-Api.实际上这个Api提供了Server对进来的Http-requests进行处理及反应的自定义Flow或者转换函数的接入界面.我们看看下面官 ...

  7. 【转】 bio 与块设备驱动

    原文地址: bio 与块设备驱动      系统中能够随机访问固定大小数据片(chunk)的设备被称作块设备,这些数据片就称作块.块设备文件都是以安装文件系统的方式使用,此也是块设备通常的访问方式.块 ...

  8. mysql 中select for update 锁表的范围备注

    mysql的锁表范围测试 1.主键明确时,行级锁: 解释:指定主键并且数据存在时,仅锁定指定的行,其它行可以进行操作 实例:指定了锁定id=1的行且数据存在①,在更新1时lock wait超时②,但是 ...

  9. Java并发编程之显式锁机制

    我们之前介绍过synchronized关键字实现程序的原子性操作,它的内部也是一种加锁和解锁机制,是一种声明式的编程方式,我们只需要对方法或者代码块进行声明,Java内部帮我们在调用方法之前和结束时加 ...

  10. js面向对象知识点之对象属性 创建对象 总结中

    昨天面试出了一道面试题 本人我做错了 于是痛定思痛 再过一遍面向对象 var name="一体机"; var value="infolist"; //构造函数 ...