java 使用Phaser取代CyclicBarrier和CountDownLatch

是什么?为什么?怎么做?

 

一、是什么

Phaser是Java7中引入的一个全新灵活的线程同步机制,包含了CyclicBarrier和CountDownLatch的相关功能(也就是说,可以使用Phaser代替CyclicBarrier和CountDownLatch)。

如果你需要等待某些线程结束,然后继续执行其他任务,那么就可以使用Phaser。

二、为什么

Phaser和CyclicBarrier、CountDownLatch有什么不同?有哪些优点?

Phaser的特点在这里有提及:

http://www.xie4ever.com/2017/03/25/java-phaser%E7%9A%84%E4%BD%BF%E7%94%A8/

三、怎么做

1.先看一个简单例子

输出结果:

在Phaser内有2个重要状态,分别是phase和party。phase就是阶段,初值为0,当所有的线程执行完本轮任务,同时开始下一轮任务时,意味着当前阶段已结束,进入到下一阶段,phase的值自动加1。party是线程,party=5意味着Phaser对象当前管理着5条线程。

2.使用Phaser代替CyclicBarrier

继续使用赛跑的例子,如果使用CyclicBarrier,就是这样:

如果换用Phaser,那就改成:

这里的arriveAndAwaitAdvance方法:顾名思义,线程执行这个方法之后将会进入等待状态。直到phaser管理的所有线程执行了这个方法,phaser中的计数重置为0,阶段数+1,所有处于等待状态下的线程继续向下执行。

3.使用Phaser代替CountDownLatch

继续使用赛跑的例子,如果使用CountDownLatch,就是这样:

如果换用Phaser,那就改成:

这里需要搞明白arrive和awaitAdvance方法的使用:

(1)arrive

告诉phaser对象,当前线程已经执行到这里了,而且将继续执行下去,并且让phaser中的计数器+1。这里设置了new Phaser(5),只要有5条线程执行了arrive方法,计数器将重置为0,phaser的阶段+1。

(2)awaitAdvance

阻塞当前线程,等待当前阶段下其他所有的线程都到达。这个方法需要传入一个参数,如果这个参数和phaser当前周期相同,那么当前线程将进入等待状态。如果这个参数和phaser当前周期不同,那么当前线程立刻结束等待,继续执行。

我个人认为这两个方法是这样作用的:

主线程开启所有子线程后,执行awaitAdvance方法:

因为子线程还在执行,所以当前的phaser阶段为0,实际上执行为:

因为当前phaser阶段=0,传入的参数也为0,所以当前线程进入阻塞状态,等待当前阶段所有线程到达。

此时,所有子线程陆续执行arrive方法,每执行一次,phaser中计数器+1。当所有线程都到达(即计数器=设定的值),计数器重置为0,phaser阶段+1,唤醒当前周期所有被awaitAdvance阻塞的方法,让主线程继续执行。

4.为什么awaitAdvance(readyPhaser.getPhase())?

既然这里的readyPhaser.awaitAdvance(readyPhaser.getPhase())等同于readyPhaser.awaitAdvance(0),为什么不干脆使用后者?

在单次执行中,这样使用确实没什么关系。但是如果存在多次执行(比如一个for/while循环),这样写就是错误的。试想,因为有多次执行,所有phaser肯定会有多个周期,如果一直readyPhaser.awaitAdvance(0),那么所有的awaitAdvance方法一定会立即返回,起不到阻塞的效果。

我的结论是:

awaitAdvance(readyPhaser.getPhase())这样写比较科学,应该这样配合使用。

四、总结

Phaser确实更加灵活,但是相对来说也更加复杂,所以需要更加谨慎地使用。

发表评论

电子邮件地址不会被公开。 必填项已用*标注