前面介绍的几个工具类大家或多或少都有听说过,但是Phaser却很少人知道,如果在面试中问到你能回答上来,会让面试官高看一眼的。

使用场景

现实中很多事情都是分很多个阶段,并且每个阶段都要都完成才能进行下一个阶段。比如就像大学时去吃饭一样,寝室几个都是在11点左右准备去吃午饭,首先是大家都要起床,然后一起出门,一起去吃完饭,最后一起回家。每一个过程中都有完成的先后,但是都会等待一起完成才会做接下来的事情。

每个阶段的等待好像都可以用上一篇文章讲的CyclicBarrier来实现,但是在这个场景下,由于有多个阶段,所以用CyclicBarrier难免有点复杂了。就可以利用Phaser来实现

用Phaser来实现

直接上代码吧,代码比较简单,没有设置额外的信息,具体代码如下图:

java也可以用opencv库吗(让人高看一眼的并发工具类Phaser)(1)

首先"phaser.bulkRegister(4);"与CyclicBarrier设置调用最大await方法次数一样,这里的意思是当phaser的arrive系列方法执行4次后当前阶段完成。

Person实现Runnable,有4个方法getUp()、goOut()、eat()、goHome()来表示做一个事情的4个阶段,每个方法在执行的最后调用"phaser.arriveAndAwaitAdvance();"来等待其他人完成,run()方法按顺序执行这4个方法。

new出4个Person并开始执行方法,4个对象所有前一个方法执行完成后才会开始执行下一个方法。执行结果如下:

id=2起床

id=1起床

id=0起床

id=3起床

id=3出门

id=0出门

id=2出门

id=1出门

id=1吃饭

id=2吃饭

id=0吃饭

id=3吃饭

id=3回家

id=2回家

id=0回家

id=1回家

可以看到几个线程相同的方法执行先后顺序不一样,但是肯定是所有同样的方法执行完成后才开始执行下一个方法。

结构分析

Phaser有三个关键属性,首先是long类型的属性state,高位的32位用来保存Phaser进行在第几个阶段,然后接下来的16位用来保存当前阶段最大参与线程数量,最低16用来保存当前阶段已经到达的线程数量,当为0是会唤醒阻塞线程。

evenQ和oddQ属性是链表结构,存储已完成处于等待的队列,与AQS中的Node类似,包含有一个Thead属性,用来保存线程用的,当最后一个参与者完成任务后唤醒队列中的线程继续执行下一个阶段的任务,或者结束任务。这里用两个链表是为了在单双阶段交替使用。

分析arriveAndAwaitAdvance方法

Phaser提供了很多方法,这里分析最重要的一个方法,理解原理其他方法应该也差不多了。源码主要的流程图如下图:

java也可以用opencv库吗(让人高看一眼的并发工具类Phaser)(2)

主要流程是:

(1)从state中计算出当前的阶段phaser和未到达的线程数量unarrived;

(2)修改state的值减一,因为unarrived是保存在state的最后16位,所以state减一就表示unarrived减一;

(3)unarrived大于1则调用internalAwaitAdvance阻塞线程;

(4)否则说明unarrived等于1,也就是当前线程是最后一个达到的,所以调用onAdvance唤醒阻塞线程;

(5)最后两步就是修改state的值,实际上就是为下一个阶段做准备,最后再次唤醒等待线程,实际上onAdvance也做了这些事情,这里是多次保证数据完整。

总结

可以看到Phaser与AQS下的工具类也差不多,都是维护一个state和同步队列,不过Phaser的state是long类型,前32为用来表达第几个阶段,紧接着16为用来表达最大线程数,最后16为用来表达未到达的线程数,也就是还可以有多少个线程调用这个方法就会是否所有阻塞的线程,并进入下一个阶段。

把state分成三个部分,在每个阶段只用-1就行,当最后16为减到0时,就进入下一个阶段,也就是把高位32为 1,低位16设置成中间的16位,实现重置阶段并记录进行的阶段。

Phaser相比CyclicBarrier或者CountDownLatch的优势是可以实现更多阶段的控制;同时Phaser每个阶段的任务数量可以控制(这篇文章这个不是重点,有兴趣可以去了解),而一个CyclicBarrier或者CountDownLatch的数量一旦确定不可修改。

Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!

java也可以用opencv库吗(让人高看一眼的并发工具类Phaser)(3)

,