本系列之前的标题是“面试知识点”,由于一些知识点并不都是面试时才用到,却是日常开发经常出现的,比如CyclicBarriar和Phaser,所以后续会将这个系列的标题改为“知识点干货”,希望能给大家带来更多优质的学习干货。
继续总结多线程同步常用的方法或者类,之前介绍了CountDownLatch,CyclicBarriar和Exchanger,这次介绍一个能同时替代CountDownLatch和CyclicBarriar的新类。
Phaser的一个特别典型的应用场景是:它可以替代使用CountDownLatch和CyclicBarriar的任何场景。例如,在进行高并发多线程测试时,有时需要控制线程的启动时机,让它们能够同时启动,从而获得最大程度的并发,通常我们会使用CountDownLatch,这个完全可以使用Phaser进行替代。再如,我们需要某些任务在某一个阶段同时执行,再所有任务执行完成后, 再同时进行到下一个阶段继续执行,我们首先想到的是CyclicBarriar,而使用Phaser也可以完全代替CyclicBarriar。
Phaser是jdk1.7上新增的一个类,它是一个灵活的多线程同步工具,包含了CyclicBarrier和CountDownLatch的大部分功能,同时也具有一些它们不支持的特性。
在使用它之前,需要了解两个概念,分别是phase和party。
phase就是阶段的意思,初值为0。当所有的线程执行任务时,它是分阶段的,每个阶段对应一个phase,当所有线程执行完本轮任务后,表示本阶段结束,进入到下一阶段,phase的值自动加1。
而party表示线程的个数,party=3表示Phaser对象当前管理着3个线程。通过方法可以
动态增parties计数,而这一点CyclicBarrier类是做不到的,它的线程数是在创建时设置好的。
Phaser对外提供的方法很多,我们只介绍几个重要的。
无参构造函数,创建一个Phaser对象。默认parties个数为0。后续可以通过register()、bulkRegister()方法来修改新的parties。在每个Phaser实例内部,会持有几个状态数据:终止状态、已经注册的parties个数、当前phase下已到达的parties个数、当前phase阶段数。
有参构造函数,一个Phaser对象,并初始一定数量的parties,相当于在初始化Phaser实例后,再regsiter此数量的parties。
一直阻塞,等待当前phase下其他parties到达。parties必须大于0,如果使用如果Phaser()创建实例,parties没有被register具体的值,调用此方法将会抛出异常。此方法同时返回当前phase周期数,如果Phaser实例已经终止,则返回负数。
阻塞方法,等待phase周期数下其他所有的parties都到达,参数指定了当前阻塞的phase周期阶段数。如果指定的phase与Phaser实例当前的phase不一致,会立即返回。可以这样来使用awaitAdvance(arrive())。
到达某个状态并且阻塞直到其他parties都到达,且advance。此方法等同于调用awaitAdvance(arrive())。
阻塞方法,同awaitAdvance,并且支持interrupted响应,参数指明是那个阶段。waiter线程可以被外部中断,在被中断时此方法立即返回,并抛出InterrutedException异常。
阻塞方法,同awaitAdvance,支持timeout类型的interrupted响应,参数指明是那个阶段。当当前线程阻塞等待timeout时长后,抛出TimeoutException异常。
如果你希望阻塞机制支持timeout、interrupted响应,可以使用类似的awaitAdvanceInterruptibly两种重载的方法。
这个方法很重要,是一个回调方法。当进入下一个phase时会回调到这个方法中进行处理。如果它返回true表示此Phaser应该终止(Phaser的状态设为termination),否则可以继续进行。phase参数表示当前周期数,registeredParties表示当前已经注册的parties个数。
大多情况下,开发者应该重写此方法,来实现自定义的advance回调处理机制。
注册一个party,每调用一次就会使Phaser实例内部Paties数量加1。如果此时onAdvance方法正在执行,此方法将会等待它执行完毕后才会返回。此方法返回当前的phase周期数,如果Phaser已经中断,将会返回负数。
进行批量注册多个parties,其它功能和register()相同。
获取到达的parties个数。
获取当前phase周期数,如果Phaser已经中断,则返回负值。
获取注册的parties个数。
获取还未到达的parties个数。
Phaser的使用场景很多,我们这里只举一个例子。
//创建时,指定参与的parties个数
int parties = 3;
//也可以在创建时不指定parties,通过使用register或者bulkRegister随时注册
Phaser phaser = new Phaser();
//主线程先注册一个party,因为register()执行一次只能创建一个party
//类似与CountDownLatch,在主线程中等待所有的parties到达后再解除阻塞
phaser.register();
ExecutorService executor = Executors.newFixedThreadPool(parties);
for(int i = 0; i < parties; i++) {
phaser.register();//每创建一个线程,我们就注册一个party
final int threadId = i;
executor.execute(new Runnable() {
@Override
public void run() {
try {
int i = 0;
//2的意思是最大周期
while (i < 2 && !phaser.isTerminated()) {
System.out.println("current threadId :"+threadId+",phase:" + phaser.getPhase());
//模拟每个线程进行的工作
Thread.sleep(1000);
//等待同一周期内其它线程到来,然后进入新的周期,从而同步进行
phaser.arriveAndAwaitAdvance();
i++;
}
} catch (Exception e) {
e.printStackTrace();
}
finally {
phaser.arriveAndDeregister();
}
}
});
}
//主线程到达,且注销自己,线程池中的线程可开始分周期进行执行
System.out.println("main thread arriveAndDeregister!");
phaser.arriveAndDeregister();
执行结果应该是如下这样的格式,
main thread arriveAndDeregister!
current threadId :0,phase:0
current threadId :1,phase:0
current threadId :2,phase:0
current threadId :2,phase:1
current threadId :0,phase:1
current threadId :1,phase:1
综上,我们可以使用Phaser替代CountDownLatch。对于CountDownLatch而言,
它的两个重要的方法,await()方法和countDown(),在Phaser中,可以用awaitAdvance(int n)和arrive()分别来代替,前者会使线程进入等待状态,后者使计数器减一,当计数器为0时所有等待的线程开始执行。
用Phaser替代CyclicBarrier更简单,CyclicBarrier的await()方法可以直接用Phaser的arriveAndAwaitAdvance()方法替代。
所以,在使用CountDownLatch或者CyclicBarrier时,我们可以考虑使用Phaser来代替,效率会更高一些。
本公众号将以推送Android各种技术干货或碎片化知识,以及整理老司机日常工作中踩过的坑涉及到的经验知识为主,也会不定期将正在学习使用的新技术总结出来进行分享。每天一点干货小知识把你的碎片时间充分利用起来。