JUC学习(四):线程同步之Phaser

Phaser是Java并发库JUC中的一个组件,它弥补了CyclicBarrier的不足,允许动态调整参与者数量并划分任务阶段。文章详细介绍了Phaser的构造方法、注册过程、arrive系列方法和await系列方法的工作原理,帮助读者深入理解Phaser的线程同步机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一.简介

二.构造方法

三.doRegister方法

四.arrive系列方法

四.await系列方法


一.简介

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;
                }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值