并发编程原理与实战(十二)并发协同利器Phaser之应用举例与总结

并发编程原理与实战(一)精准理解线程的创建和停止

并发编程原理与实战(二)Thread类关键API详解

并发编程原理与实战(三)一步步剖析线程返回值

并发编程原理与实战(四)经典并发协同方式synchronized与wait+notify详解

并发编程原理与实战(五)经典并发协同方式伪唤醒与加锁失效原理揭秘

并发编程原理与实战(六)详解并发协同利器CountDownLatch

并发编程原理与实战(七)详解并发协同利器CyclicBarrier

并发编程原理与实战(八)详解并发协同利器Semaphore

并发编程原理与实战(九)限流利器信号量的最佳实践分析

并发编程原理与实战(十)并发协同利器Phaser之概念详解

并发编程原理与实战(十一)并发协同利器Phaser之方法详解

上一篇讲解了Phaser的几类核心方法,本文来讲解下怎么用这些方法,分析如何将这些方法运用到具体的实际场景中。

多阶段多线程调用接口问题梳理

Phaser的主要使用场景是多个线程多个阶段并发协同运行,并且可以调整每个阶段的线程数量,如果把Phaser用在“不同阶段(时段)多线程调用接口“这个场景中,我们列一下可能会产生哪些问题:

1、不同阶段的屏障怎么表示?

2、不同阶段的阶段号怎么表示?

3、如何指定不同阶段的线程数量?如何在某个阶段动态增加线程数量?

4、每个阶段多个线程同时调用接口怎么实现?

5、当前阶段调用完成后如何进入到下一个阶段?

6、如何在每个阶段完成后,自定义一个动作实现指定业务逻辑?如打印当前的阶段号,输出当前阶段的参与线程数量等。

7、在某个阶段增加参与线程后,如何让这些增加的线程不参与后面阶段的线程的调用?

多阶段多线程调用接口问题分析

1、通过前面两篇文章我们已经知道,Phaser是一个可重复使用的同步屏障,功能类似于CyclicBarrier,创建一个Phaser对象就是创建一个屏障,可以指定参与线程的数量,既然可以重复使用,那么所有阶段都是使用同一个屏障,并不需要每个阶段设置一个屏障。所以不同阶段的屏障就用一个Phaser对象来表示。

2、创建Phaser屏障时,默认的阶段号从0开始,最大值是Integer.MAX_VALUE,所以不同阶段的阶段号是Phaser内部自动维护的,并不需要我们手动设置变量来表示。初始阶段的参与线程都完成了当前阶段的任务后阶段号自动增加。

3、创建Phaser屏障时,初始阶段可以指定参与线程数量,后面的阶段通过注册( register()方法或bulkRegister(int parties)‌方法)与注销类方法(arriveAndDeregister()‌方法)来灵活调整参与线程的数量,无需每个阶段用一个变量来指定参与线程数量。可以通过调用isTerminated()方法判断屏障的运行状态和获取当前的阶段号,在指定的阶段增加参与者。

4、调用arriveAndAwaitAdvance()方法表明线程已经达到屏障点完成了当前阶段的任务,并等待其他线程到达,类似CyclicBarrier的await()方法,当全部参与线程到达后自动触发“同时调用”。

5、当前阶段的全部参与线程调用了arriveAndAwaitAdvance()方法后,自动进入下一个阶段。

6、通过前文我们已经知道,重写onAdvance(int phase, int registeredParties)‌方法可以控制阶段转换时的行为逻辑,所以可以通过该方法实现在阶段完成时的业务逻辑。

7、注销参与线程的方法只有arriveAndDeregister()‌一个,如果想让当前线程不参与后面阶段的运行,可以在任务完后调用该方法。

多阶段多线程调用接口编码实现

分析完成上述问题以及解决办法后,下面我们用代码实现。

public class PhaseSyncDemo {
    // 总阶段数
    private static final int PHASE_COUNT = 3;
    // 初始参与线程数
    private static final int THREAD_COUNT = 4;
    // 动态增加的参与线程数
    private static final int ADD_THREAD_COUNT = 3;
    public static void main(String[] args) {
        //创建多阶段屏障
        Phaser phaser = new Phaser(THREAD_COUNT) {
            //重写onAdvance方法,定义阶段完成时的动作
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("\n====== 当前阶段" + phase + "," + registeredParties + "个参与者,在屏障点集合完成,准备发起调用 ======");
                //所有阶段完成或者参与者数量为0,屏障终止
                return phase >= PHASE_COUNT - 1 || registeredParties == 0;
            }
        };

        //创建3个阶段,每个阶段初始化3个参与线程
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int phase = 0; phase < PHASE_COUNT; phase++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //线程到达阶段屏障点并等待
                    phaser.arriveAndAwaitAdvance();
                    //多个线程同时到达屏障点后同步调用接口
                    callApi();
                }
            }, "Thread-" + i).start();
        }

        //判断相位器是否已经终止
        while (!phaser.isTerminated()) {
            //判断当前阶段号是否为1,为1则动态增加3个参与线程
            if (phaser.getPhase() == 1) {
                phaser.bulkRegister(ADD_THREAD_COUNT);
                for(int i=0;i<ADD_THREAD_COUNT;i++) {
                    new Thread(() -> {
                        //动态增加的线程到达屏障点并等待
                        phaser.arriveAndAwaitAdvance();
                        callApi();

                        //动态增加的线程完成任务后注销离开,不参与下一个阶段
                        phaser.arriveAndDeregister();
                    }, "Add-Thread-" + i).start();
                }
                break;
            }
        }
    }

    private static void callApi() {
        System.out.println("时间戳" + System.currentTimeMillis() + "," + Thread.currentThread().getName() + "调用接口");
    }
}

运行结果:

====== 当前阶段04个参与者,在屏障点集合完成,准备发起调用 ======
时间戳1748494161912,Thread-1调用接口
时间戳1748494161912,Thread-0调用接口
时间戳1748494161912,Thread-2调用接口
时间戳1748494161912,Thread-3调用接口

====== 当前阶段17个参与者,在屏障点集合完成,准备发起调用 ======
时间戳1748494162918,Thread-2调用接口
时间戳1748494162918,Add-Thread-0调用接口
时间戳1748494162918,Thread-0调用接口
时间戳1748494162918,Add-Thread-1调用接口
时间戳1748494162918,Thread-3调用接口
时间戳1748494162918,Add-Thread-2调用接口
时间戳1748494162918,Thread-1调用接口

====== 当前阶段24个参与者,在屏障点集合完成,准备发起调用 ======
时间戳1748494163933,Thread-2调用接口
时间戳1748494163933,Thread-3调用接口
时间戳1748494163933,Thread-1调用接口
时间戳1748494163933,Thread-0调用接口

从运行结果可以看出,阶段0只有初始化的4个线程参与,阶段1的后增加了3个参与线程,这三个线程只参与阶段1的调用,阶段2又恢复回初始化的4个线程参与。

从代码中可以看出,当创建Phaser屏障对象时指定了参与线程的数量后,直接创建线程并在线程的任务代码中通过调用屏障的arriveAndAwaitAdvance()方法表明线程参与进来了,并不需要显示的调用register()方法或者bulkRegister(int parties)‌方法注册线程;而在阶段运行的过程中动态添加的线程,则需要先调用注册方法进行注册。

总结

到目前为止,我们已经学习了CountDownLatch、CyclicBarrier、Semaphore、Phaser这四个多线程并发协同工具类,下面总结下这四个并发协同工具类的使用场景和特性。

工具类使用场景特性
CountDownLatch主线程等待一组子线程执行完成后继续执行、一组线程需要等待统一的指令后同时执行倒计时锁存器,单向计数器递减,不可重复使用,无动态线程调整参与线程数量的能力
CyclicBarrier一组线程在屏障点互相等待,全部到达后同时释放线程继续执行;需要多次执行“等待-释放”动作的场景固定的参与线程数,屏障支持重置和重复使用
Semaphore通过信号量控制并发访问共享资源的线程数量无分阶段运行能力,仅仅控制并发的线程数量,支持公平和非公平模式排队访问共享资源
Phaser一组线程在屏障点互相等待,全部到达后同时释放线程继续下一个阶段的执行,需要运行中动态注册或注销参与线程具有和CyclicBarrier一样的特性,支持无限阶段推进、分层结构和运行时动态调整参与者,灵活性最高
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帧栈

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值