目录
一.简介
CyclicBarrier解决了CDL不能重用的问题,但是仍有以下不足:
1)不能动态调整计数器值,假如线程数不足以打破barrier,就只能reset或者多加些线程,在实际运用中显然不现实
2)每次await仅消耗1个计数器值,不够灵活
Phaser就是用来解决这些问题的。Phaser将多个线程协作执行的任务划分为多个阶段,每个阶段都可以有任意个参与者,线程可以随时注册并参与到某个阶段。下面给出一个例子:
import java.util.Random;
import java.util.concurrent.Phaser;
public class PhaserTest {
private static Phaser phaser = new Phaser(3);
public static void testMethod(){
while(true){
System.out.println(Thread.currentThread().getName()+",Phase:"+phaser.getPhase()+",now:"+System.currentTimeMillis());
try {
Thread.sleep(new Random().nextInt(5)*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
phaser.arriveAndAwaitAdvance();
System.out.println(Thread.currentThread().getName()+",Phase:"+phaser.getPhase()+",now:"+System.currentTimeMillis());
}
}
public static void main(String args[]) {
Thread a = new Thread(()->testMethod(),"A");
a.start();
Thread b = new Thread(()->testMethod(),"B");
b.start();
Thread c = new Thread(()->testMethod(),"C");
c.start();
}
}
输出如下:
A,Phase:0,now:1550904587079
B,Phase:0,now:1550904587080
C,Phase:0,now:1550904587080
A,Phase:1,now:1550904591081
B,Phase:1,now:1550904591081
C,Phase:1,now:1550904591081
B,Phase:1,now:1550904591082
A,Phase:1,now:1550904591081
C,Phase:1,now:1550904591082
...
可以看到,每次输出时,三个线程的时间戳都是一样的。显然每调用一次arriveAndAwaitAdvance,phase值就会加1,代表进入了新一轮同步。
二.构造方法
Phaser有多个构造方法,不过实际执行的是同一个:
public Phaser(Phaser parent, int parties) {
if (parties >>> PARTIES_SHIFT != 0)
throw new IllegalArgumentException("Illegal number of parties");
int phase = 0;
this.parent = parent;
if (parent != null) {
final Phaser root = parent.root;
this.root = root;
this.evenQ = root.evenQ;
this.oddQ = root.oddQ;
if (parties != 0)
phase = parent.doRegister(1);
}
else {
this.root = this;
this.evenQ = new AtomicReference<QNode>();
this.oddQ = new AtomicReference<QNode>();
}
this.state = (parties == 0) ? (long)EMPTY :
((long)phase << PHASE_SHIFT) |
((long)parties << PARTIES_SHIFT) |
((long)parties);
}
首先进行了一次无符号右位移,PARTIES_SHIFT为16。Phaser将state(long类型)划分为四段:
位置 | 作用 |
---|---|
0~15 位 | 表示还没有到达的线程 |
16~31 位 | 表示一共有多少线程需要同步 |
32~62 位 | 表示当前是第几阶段 |
63 位 | 标记该Phaser是否已经结束 |
所以该操作的目的是检验parties是否为正数(正数无符号右移,最终结果一定是0)。
接下来的代码是构造Phaser树和state值的过程。第一次创建的Phaser必然是整棵树的根节点,之后可以通过两种方式动态调整parties的数量:1)通过register()和bulkRegister(int parties)方法来动态调整注册任务的数量;2)构造新的Phaser,然后注册到已有Phaser上。
QNode是Phaser的内部类,使用到了ForkJoin框架。
三.doRegister方法
首先来看一下Phaser是如何注册的。
long adjust = ((long)registrations << PARTIES_SHIFT) | registrations;
final Phaser parent = this.parent;
int phase;
registrations是一个int值,强制转换为long后,高32位补上0,再左移16位,并和原值进行位或操作。从而得到了调整后的state值。然后获取当前节点的父节点。接下来是一个自旋:
for (;;) {
long s = (parent == null) ? state : reconcileState();
int counts = (int)s;
int parties = counts >>> PARTIES_SHIFT;
int unarrived = counts & UNARRIVED_MASK;
if (registrations > MAX_PARTIES - parties)
throw new IllegalStateException(badRegister(s));
phase = (int)(s >>> PHASE_SHIFT);
if (phase < 0)
break;
...
}
假如父节点为空,说明当前节点为根节点,直接获取state,否则使用reconcileState方法获取根节点state值。然后基于该值计算等待线程总数、未到达线程数、阶段数,再进行校验。
接下来分三种情况处理:
首先是counts(state低32位)不为0的情况,这种情况说明已经有Phaser注册过了,需要先看看当前的状态能否执行注册操作:
if (counts != EMPTY) { // not 1st registration
if (parent == null || reconcileState() == s) {
if (unarrived == 0) // wait out advance
root.internalAwaitAdvance(phase, null);
else if (UNSAFE.compareAndSwapLong(this, stateOffset,
s, s + adjust))
break;
}
}
如果当前没有未到达线程,说明本阶段可以结束,可以开始下一阶段,否则就要CAS地修改state值。
如果parent为空,说明是第一次注册,此时直接CAS地修改state的phase值即可。
else if (parent == null) { // 1st root registration
long next = ((long)phase << PHASE_SHIFT) | adjust;
if (UNSAFE.compareAndSwapLong(this, stateOffset, s, next))
break;
}
剩下的情况就是,已经有一个Phaser了,不过该Phaser创建时,没有指定parties值,即同步线程数为0。这种情况下由于没有线程运行,可以不考虑性能,直接synchronized:
else {
synchronized (this) { // 1st sub registration
if (state == s) { // recheck under lock
phase = parent.doRegister(1);
if (phase < 0)
break;
while (!UNSAFE.compareAndSwapLong
(this, stateOffset, s,
((long)phase << PHASE_SHIFT) | adjust)) {
s = state;
phase = (int)(root.state >>> PHASE_SHIFT);
}
break;
}
}
}
在同步块中,向父节点注册了1个同步线程数,并修改了阶段数。
四.arrive系列方法
Phaser的关键就是arriveXXX方法。
首先来看arrive()方法:
public int arrive() {
return doArrive(ONE_ARRIVAL);
}
实际调用了doArrive方法,传入一个常量,其值为1。
doArrive方法首先获取了Phaser树的根节点,然后自旋处理,下面分段解说。
第一段代码的主要作用是计算各部分的值,类似doRegister方法,不再赘述。
long s = (root == this) ? state : reconcileState();
int phase = (int)(s >>> PHASE_SHIFT);
if (phase < 0)
return phase;
int counts = (int)s;
int unarrived = (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK);
if (unarrived <= 0)
throw new IllegalStateException(badArrive(s));
第二段代码是一个if块,条件是:
UNSAFE.compareAndSwapLong(this, stateOffset, s, s-=adjust)
也就是CAS地修改state值,如果修改成功,那么就要看看当前到达的线程是不是最后一个到达终点的。如果不是,说明还没进入下一阶段,可以直接返回阶段数;否则就要修改阶段数以进行下一轮同步:
long n = s & PARTIES_MASK; // base of next state
int nextUnarrived = (int)n >>> PARTIES_SHIFT;
if (root == this) {
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;
UNSAFE.compareAndSwapLong(this, stateOffset, s, n);
releaseWaiters(phase);
}
else if (nextUnarrived == 0) { // propagate deregistration
phase = parent.doArrive(ONE_DEREGISTER);
UNSAFE.compareAndSwapLong(this, stateOffset,
s, s | EMPTY);
}
else
phase = parent.doArrive(ONE_ARRIVAL);
arriveAndDeregister就简单了,和arrive方法唯一的区别就是传给doArrive的值不同,传入的是1 | 1<<16,根据上面所述,CAS修改state值时,新值是 s-adjust,所以该方法就是将未到达线程数和总线程数都减1。
arriveAndAwaitAdvance方法内容和doArrive类似:
public int arriveAndAwaitAdvance() {
...
if (UNSAFE.compareAndSwapLong(this, stateOffset, s,
s -= ONE_ARRIVAL)) {
if (unarrived > 1)
return root.internalAwaitAdvance(phase, null);
if (root != this)
return parent.arriveAndAwaitAdvance();
...
}
不同的大概就上面这点,也很容易理解:每次调用该方法都将未到达线程数减1,其他值不变。如果还有未到达线程,则继续等待;如果所有线程都到达了,通过父节点的相同方法逐级上溯,最终调用根节点相同方法,进入下一轮同步。
四.await系列方法
当仍有线程未到达时,已到达的线程需要阻塞等待,这时候就需要await系列方法。
await系列有两个版本,可中断(awaitAdvance,接受int参数,和当前phase比较,如果相同就等待,否则继续运行)和不可中断(awaitAdvanceInteruptibly),其中不可中断版本还有一个可超时的重载版本。不过这些方法万变不离其宗,最后调用的都是root节点的internalAwaitAdvance方法。
首先清除上一阶段的队列:
releaseWaiters(phase-1);
然后判断是否还在本阶段,是则继续处理;否则判断node是否为空(不为空说明可以中断),如果node为空,就处理中断or唤醒等待队列:
while ((p = (int)((s = state) >>> PHASE_SHIFT)) == phase) {
...
}
if (node != null) {
if (node.thread != null)
node.thread = null; // avoid need for unpark()
if (node.wasInterrupted && !node.interruptible)
Thread.currentThread().interrupt();
if (p == phase && (p = (int)(state >>> PHASE_SHIFT)) == phase)
return abortWait(phase); // possibly clean up on abort
}
releaseWaiters(phase);
return p;
如果仍然处在本阶段,就分四种情况处理:
1)处于不可中断模式:
if (node == null) { // spinning in noninterruptible mode
int unarrived = (int)s & UNARRIVED_MASK;
if (unarrived != lastUnarrived &&
(lastUnarrived = unarrived) < NCPU)
spins += SPINS_PER_ARRIVAL;
boolean interrupted = Thread.interrupted();
if (interrupted || --spins < 0) { // need node to record intr
node = new QNode(this, phase, false, false, 0L);
node.wasInterrupted = interrupted;
}
}
lastUnarrived为0,unarrived!=lastUnarrived说明还有线程没到达,此时如果遇到中断信号,由于不可中断,只能新建一个QNode记录下来,继续循环。
2)如果线程全部到达,或者放弃同步,则退出循环:
else if (node.isReleasable()) // done or aborted
break;
3)如果传入的node不在线程队列中,就入列(如果node不为空且还在等待,那就必定会进一次这个块,因为queued初始化为false):
else if (!queued) { // push onto queue
AtomicReference<QNode> head = (phase & 1) == 0 ? evenQ : oddQ;
QNode q = node.next = head.get();
if ((q == null || q.phase == phase) &&
(int)(state >>> PHASE_SHIFT) == phase) // avoid stale enq
queued = head.compareAndSet(q, node);
}
4)其他情形:
调用ForkJoinPool处理,实际就是自旋等待
try {
ForkJoinPool.managedBlock(node);
} catch (InterruptedException ie) {
node.wasInterrupted = true;
}