前言

  JDK中为了处理线程之间的同步问题,除了提供锁机制之外,还提供了几个非常有用的并发工具类:CountDownLatch、CyclicBarrier、Semphore、Exchanger、Phaser;

  CountDownLatch、CyclicBarrier、Semphore、Phaser 这四个工具类提供一种并发流程的控制手段;而Exchanger工具类则提供了在线程之间交换数据的一种手段。

简介

  Phaser 是JDK1.7版本中新增的,是一个可重用的同步barrier,它的功能与 CountDownLatch、CyclicBarrier 相似,但是使用起来更加灵活。可以用来解决控制多个线程分阶段共同完成任务的情景问题。

  Phaser中有两个重要的计数:

  • phase:当前的周期索引(或者 阶段索引),初始值为0,当所有线程执行完本阶段的任务后,phase就会加一,进入下一阶段;可以结合onAdvance()方法,在不同的阶段,执行不同的屏障方法。
  • parties:注册的线程数,即Phaser要监控的线程数量,或者说是 建立的屏障的数量。屏障的数量不是固定的,每个阶段的屏障的数量都可以是不一样。

下面详细介绍Phaser一些机制

1、Registration(注册机制):与其他barrier不同的是,Phaser中的“注册的同步者(parties)”会随时间而变化,Phaser可以通过构造器初始化parties个数,也可以在Phaser运行期间随时加入(方法 register( ), bulkRegister(int) )新的parties,以及在运行期间注销(方法 arriveAndDeregister( ) )parties。运行时可以随时加入、注销parties,只会影响Phaser内部的计数器,它建立任何内部的bookkeeping(账本),因此task不能查询自己是否已经注册了,当然你可以通过实现子类来达成这一设计要求。

2、Synchronization(同步机制):类似于CyclicBarrier,Phaser也可以awaited多次,它的arrivedAndAwaitAdvance()方法的效果类似于CyclicBarrier的await()。Phaser的每个周期(generation)都有一个phase数字,phase 从0开始,当所有的已注册的parties都到达后(arrive)将会导致此phase数字自增(advance),当达到Integer.MAX_VALUE后继续从0开始。这个phase数字用于表示当前parties所处于的“阶段周期”,它既可以标记和控制parties的wait行为、唤醒等待的时机。

  • Arrival:Phaser中的arrive()、arriveAndDeregister()方法,这两个方法不会阻塞(block),但是会返回相应的phase数字,当此phase中最后一个party也arrive以后,phase数字将会增加,即phase进入下一个周期,同时触发(onAdvance)那些阻塞在上一phase的线程。这一点类似于CyclicBarrier的barrier到达机制;更灵活的是,我们可以通过重写onAdvance方法来实现更多的触发行为。
  • Waiting:Phaser中的awaitAdvance()方法,需要指定一个phase数字,表示此Thread阻塞直到phase推进到此周期,arriveAndAwaitAdvance()方法阻塞到下一周期开始(或者当前phase结束)。不像CyclicBarrier,即使等待Thread已经interrupted,awaitAdvance方法会继续等待。Phaser提供了Interruptible和Timout的阻塞机制,不过当线程Interrupted或者timout之后将会抛出异常,而不会修改Phaser的内部状态。如果必要的话,你可以在遇到此类异常时,进行相应的恢复操作,通常是在调用forceTermination()方法之后。

    Phaser通常在ForJoinPool中执行tasks,它可以在有task阻塞等待advance时,确保其他tasks的充分并行能力。

3、Termination(终止):Phaser可以进入Termination状态,可以通过isTermination()方法判断;当Phaser被终止后,所有的同步方法将会立即返回(解除阻塞),不需要等到advance(即advance也会解除阻塞),且这些阻塞方法将会返回一个负值的phase值(awaitAdvance方法、arriveAndAwaitAdvance方法)。当然,向一个termination状态的Phaser注册party将不会有效;此时onAdvance()方法也将会返回true(默认实现),即所有的parties都会被deregister,即register个数为0。

4、Tiering(分层):Phaser可以“分层”,以tree的方式构建Phaser来降低“竞争”。如果一个Phaser中有大量parties,这会导致严重的同步竞争,所以我们可以将它们分组并共享一个parent Phaser,这样可以提高吞吐能力;Phaser中注册和注销parties都会有Child 和parent Phaser自动管理。当Child Phaser中中注册的parties变为非0时(在构造函数Phaser(Phaser parent,int parties),或者register()方法),Child Phaser将会注册到其Parent上;当Child Phaser中的parties变为0时(比如由arrivedAndDegister()方法),那么此时Child Phaser也将从其parent中注销出去。

5、Monitoring.(监控):同步的方法只会被register操作调用,对于当前state的监控方法可以在任何时候调用,比如getRegisteredParties()获取已经注册的parties个数,getPhase()获取当前phase周期数等;因为这些方法并非同步,所以只能反映当时的瞬间状态。

Phaser的API介绍

构造方法

方法名 描述
Phaser() 构建一个Phaser
Phaser(int parties) 创建一个指定屏障数量的Phaser
Phaser(Phaser parent) 相当于 Phaser(parent, 0)
Phaser(Phaser parent, int parties) 创建一个指定屏障数量的Phaser,此phaser是注册在另一个Phaser parent下

方法摘要

方法名 描述
public int arrive() 到达此phaser的屏障点,使phaser的到达的线程数加一,但不会阻塞等待其他线程。
返回:phase值,即当前阶段(周期)的索引,或者是负值(当Phaser 停止时)
public int arriveAndDeregister() 到达此phaser的屏障点,使phaser的到达的线程数加一,并且会取消一个屏障点的注册。也不会阻塞等待其他线程。
返回:phase值,即当前阶段(周期)的索引,或者是负值(当Phaser 停止时)
public int arriveAndAwaitAdvance() 到达此phaser的屏障点,并且阻塞等待其他线程到达此屏障点。注意:这是非中断的阻塞,此方法与awaitAdvance(arrive())等同。如果你希望阻塞机制支持timeout、interrupted响应,可以使用类似的其他方法(参见下文)。如果你希望到达后且注销,而且阻塞等到当前phase下其他的parties到达,可以使用awaitAdvance(arriveAndDeregister())方法组合。
返回:phase值,即当前阶段(周期)的索引;如果Phaser 停止,则返回负值
public int awaitAdvance(int phase) 在指定的阶段(周期)phase下等待其他线程到达屏障点,注意:这是非中断的阻塞。如果指定的phase与Phaser当前的phase不一致,或者Phaser 停止了,则立即返回。
参数 phase:通常就是arrive()、arriveAndDeregister()的返回值;
public int awaitAdvanceInterruptibly(int phase)
throws InterruptedException
此方法是可中断的,其他与awaitAdvance()一致
public int awaitAdvanceInterruptibly(
int phase, long timeout,TimeUnit unit)
throws InterruptedException, TimeoutException
超时等待方法,其他与awaitAdvance()一致
public int register() 新注册一个party,导致Phaser内部registerPaties数量加1;如果此时onAdvance方法正在执行,此方法将会等待它执行完毕后才会返回。此方法返回当前的phase周期数,如果Phaser已经中断,将会返回负数。
public int bulkRegister(int parties) 批量注册多个party,与register()相似
protected boolean onAdvance(int phase, int registeredParties) barrier action(屏障方法)。如果需要,则必须继承Phaser类,重写此方法。如果返回true表示此Phaser应该终止(此后将会把Phaser的状态为termination,即isTermination()将返回true。),否则可以继续进行。phase参数表示当前周期数,registeredParties表示当前已经注册的parties个数。
默认实现为:return registeredParties == 0;在很多情况下,开发者可以通过重写此方法,来实现自定义的
public void forceTermination() 强制终止,此后Phaser对象将不可用,即register等将不再有效。此方法将会导致Queue中所有的waiter线程被唤醒。这个方法对于在一个或多个任务遇到意外异常之后协调恢复是很有用的。
public int getArrivedParties() 获取已经到达的parties个数。
public int getUnarrivedParties() 获取没有到达的parties个数。
public Phaser getParent() 获取其父亲类Phaser,没有则返回null
public Phaser getRoot() 返回该phaser的根祖先,如果没有父类,返回此phaser。
public boolean isTerminated() 如果该phaser被终止,则返回true。

@ Example1 多阶段(周期)、带屏障事件示例

  例子很简单,模拟跑步比赛的过程,分为三个阶段:1、参赛者到达起跑点,并在起跑点等待其他参赛者;2、参赛者齐人后,开始准备,并等待枪声。3、参赛这到达终点,并结束比赛,不再等待任何情况。

public class PhaserTest{

public static MyPhaser myPhaser = new MyPhaser();

	public static void main(String[] args) {
MyPhaser myPhaser = new MyPhaser();
// 一次性注册5个party,即建立5个屏障点
myPhaser.bulkRegister(5);
for (int i = 0; i < 5; i++) {
Thread runner = new Thread(new Runnable() { @Override
public void run() {
// 第一阶段(周期),phaser的周期数初始值为0
System.out.println(Thread.currentThread().getName() + "到达了起跑点!");
// 到达了屏障点(起跑点),阻塞等待其他线程
myPhaser.arriveAndAwaitAdvance(); // 继续运行,将进入第二阶段,phaser的周期数加一
System.out.println(Thread.currentThread().getName() + "准备起跑!");
// 到达了屏障点(准备起跑),阻塞等待其他线程
myPhaser.arriveAndAwaitAdvance(); // 进入第三阶段
System.out.println(Thread.currentThread().getName() + "到达了终点!");
// 参数者到达了终点,结束比赛,不再等待其他参赛者
myPhaser.arriveAndDeregister();// 取消注册一个party
}
}, "参赛者" + i + "号");
runner.start();
}
}
}

MyPhaser类,定制 barrier action(屏障事件)

public class MyPhaser extends Phaser {

	@Override
//改写onAdvance方法
public boolean onAdvance(int phase, int registeredParties) {
//判断当前的Phaser是否终止
if (!isTerminated()) {
// 分成三个阶段,在不同的阶段(周期),执行不同的屏障事件
if (phase == 0) {
// ....
System.out.println("第一阶段:所有参赛者都到达了起跑点!");
} else if (phase == 1) {
// ....
System.out.println("第二阶段:所有参赛者都已经就位,并准备好!比赛正式开始");
} else if (phase == 2) {
// ....
System.out.println("第三阶段:所有参赛者都到达终点,比赛结束!!");
}
}
return super.onAdvance(phase, registeredParties);
}
}

运行结果:

参赛者0号到达了起跑点!

参赛者3号到达了起跑点!

参赛者4号到达了起跑点!

参赛者2号到达了起跑点!

参赛者1号到达了起跑点!

第一阶段:所有参赛者都到达了起跑点!

参赛者0号准备起跑!

参赛者1号准备起跑!

参赛者2号准备起跑!

参赛者3号准备起跑!

参赛者4号准备起跑!

第二阶段:所有参赛者都已经就位,并准备好!比赛正式开始

参赛者4号到达了终点!

参赛者1号到达了终点!

参赛者0号到达了终点!

参赛者2号到达了终点!

参赛者3号到达了终点!

第三阶段:所有参赛者都到达终点,比赛结束!


@ Example2 分层示例

下面的例子:每一个Phaser周期类注册的线程数目不能超过TASKS_PER_PHASER(例子中是4个),否则就要增加一层子phaser层。

public class PhaserTest6 {
//
private static final int = 4; public static void main(String args[]) throws Exception {
//
final int phaseToTerminate = 3;
//创建一个Phaser父类对象
final Phaser phaser = new Phaser() {
@Override
protected boolean onAdvance(int phase, int registeredParties) { //屏障方法
System.out.println("====== " + phase + " ======");
return phase == phaseToTerminate || registeredParties == 0;
}
}; //创建10个任务
final Task tasks[] = new Task[10];
build(tasks, 0, tasks.length, phaser);
for (int i = 0; i < tasks.length; i++) {
System.out.println("starting thread, id: " + i);
final Thread thread = new Thread(tasks[i]);
thread.start();
}
} //递归分层,
public static void build(Task[] tasks, int lo, int hi, Phaser ph) { //如果任务的数量超过每一层的phaser的阈值TASKS_PER_PHASER,则要继续分层
if (hi - lo > TASKS_PER_PHASER) {
for (int i = lo; i < hi; i += TASKS_PER_PHASER) {
int j = Math.min(i + TASKS_PER_PHASER, hi);
//当前的phaser(ph)作为父周期,来创建一个子phaser
build(tasks, i, j, new Phaser(ph));
}
} else {
//线程的数量在阈值内,无需分成,可以直接注册线程到当前的Phaser
for (int i = lo; i < hi; ++i)
tasks[i] = new Task(i, ph);
}
} public static class Task implements Runnable {
//
private final int id;
private final Phaser phaser; public Task(int id, Phaser phaser) {
this.id = id;
this.phaser = phaser;
this.phaser.register();
} @Override
public void run() {
while (!phaser.isTerminated()) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// NOP
}
System.out.println("in Task.run(), phase: " + phaser.getPhase() + ", id: " + this.id);
phaser.arriveAndAwaitAdvance();
}
}
}
}

需要注意的是,TASKS_PER_PHASER的值取决于具体的Task实现。对于Task执行时间很短的场景(也就是竞争相对激烈),可以考虑使用较小的TASKS_PER_PHASER值,例如4。反之可以适当增大

运行结果:

in Task.run(), phase: 0, id: 2

in Task.run(), phase: 0, id: 1

in Task.run(), phase: 0, id: 3

in Task.run(), phase: 0, id: 0

in Task.run(), phase: 0, id: 8

in Task.run(), phase: 0, id: 5

in Task.run(), phase: 0, id: 9

in Task.run(), phase: 0, id: 7

in Task.run(), phase: 0, id: 4

in Task.run(), phase: 0, id: 6

====== 0 ======

in Task.run(), phase: 1, id: 9

in Task.run(), phase: 1, id: 6

in Task.run(), phase: 1, id: 1

in Task.run(), phase: 1, id: 7

in Task.run(), phase: 1, id: 8

in Task.run(), phase: 1, id: 5

in Task.run(), phase: 1, id: 0

in Task.run(), phase: 1, id: 4

in Task.run(), phase: 1, id: 3

in Task.run(), phase: 1, id: 2

====== 1 ======

in Task.run(), phase: 2, id: 6

in Task.run(), phase: 2, id: 0

in Task.run(), phase: 2, id: 2

in Task.run(), phase: 2, id: 3

in Task.run(), phase: 2, id: 7

in Task.run(), phase: 2, id: 5

in Task.run(), phase: 2, id: 8

in Task.run(), phase: 2, id: 9

in Task.run(), phase: 2, id: 1

in Task.run(), phase: 2, id: 4

====== 2 ======

in Task.run(), phase: 3, id: 3

in Task.run(), phase: 3, id: 4

in Task.run(), phase: 3, id: 9

in Task.run(), phase: 3, id: 5

in Task.run(), phase: 3, id: 8

in Task.run(), phase: 3, id: 1

in Task.run(), phase: 3, id: 7

in Task.run(), phase: 3, id: 0

in Task.run(), phase: 3, id: 2

in Task.run(), phase: 3, id: 6

====== 3 ======

参考文献:

并发工具类(五) Phaser类的更多相关文章

  1. Java并发(十五):并发工具类——信号量Semaphore

    先做总结: 1.Semaphore是什么? Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源. 把它比作是控制流量的红绿灯,比如XX马路要 ...

  2. 并发工具类(三)控制并发线程的数量 Semphore

    前言   JDK中为了处理线程之间的同步问题,除了提供锁机制之外,还提供了几个非常有用的并发工具类:CountDownLatch.CyclicBarrier.Semphore.Exchanger.Ph ...

  3. 并发工具类:CountDownLatch、CyclicBarrier、Semaphore

    在多线程的场景下,有些并发流程需要人为来控制,在JDK的并发包里提供了几个并发工具类:CountDownLatch.CyclicBarrier.Semaphore. 一.CountDownLatch ...

  4. JUC学习笔记--JUC中并发工具类

    JUC中并发工具类 CountDownLatch CountDownLatch是我目前使用比较多的类,CountDownLatch初始化时会给定一个计数,然后每次调用countDown() 计数减1, ...

  5. 并发工具类(一)等待多线程的CountDownLatch

    前言   JDK中为了处理线程之间的同步问题,除了提供锁机制之外,还提供了几个非常有用的并发工具类:CountDownLatch.CyclicBarrier.Semphore.Exchanger.Ph ...

  6. 并发工具类(二)同步屏障CyclicBarrier

    前言   JDK中为了处理线程之间的同步问题,除了提供锁机制之外,还提供了几个非常有用的并发工具类:CountDownLatch.CyclicBarrier.Semphore.Exchanger.Ph ...

  7. 并发工具类(四)线程间的交换数据 Exchanger

    前言   JDK中为了处理线程之间的同步问题,除了提供锁机制之外,还提供了几个非常有用的并发工具类:CountDownLatch.CyclicBarrier.Semphore.Exchanger.Ph ...

  8. JUC 常用4大并发工具类

    什么是JUC? JUC就是java.util.concurrent包,这个包俗称JUC,里面都是解决并发问题的一些东西 该包的位置位于java下面的rt.jar包下面 4大常用并发工具类: Count ...

  9. Java线程的并发工具类

    Java线程的并发工具类. 一.fork/join 1. Fork-Join原理 在必要的情况下,将一个大任务,拆分(fork)成若干个小任务,然后再将一个个小任务的结果进行汇总(join). 适用场 ...

随机推荐

  1. bind() 函数兼容

    为了搞清这个陌生又熟悉的bind,google一下,发现javascript1.8.5版本中原生实现了此方法,目前IE9+,ff4+,chrome7+支持此方法,opera和safari不支持(MDN ...

  2. eclipse 配置jdk和maven

    准备工作:确保已安装好jdk和maven,并完全配置环境.若是没有请参考前两篇博客: jdk:    http://www.cnblogs.com/qinbb/p/6861851.html maven ...

  3. hdu1355

    题意:有一片矩形花生田在路的一侧,田上的整数坐标位置有0个或多个花生,现规定从路上走到田地最边上的某个格点位置.从田边上走回路上.从一个格点移动到另一个格点.采摘格点上的花生,这四种动作都要花费一单位 ...

  4. MyBatis-动态SQL的if、choose、when、otherwise、trim、where、set、foreach使用(各种标签详解), 以及实体间关系配置

    比较全的文档:https://www.cnblogs.com/zhizhao/p/7808880.html 或 https://blog.csdn.net/zhll3377/article/detai ...

  5. 关联容器map(红黑树,key/value),以及所有的STL容器详解

    字符串或串(String)是由数字.字母.下划线组成的一串字符.一般记为 s=“a1a2···an”(n>=0).它是编程语言中表示文本的数据类型.在程序设计中,字符串(string)为符号或数 ...

  6. leetcode:Palindrome Number【Python版】

    一次AC 题目要求中有空间限制,因此没有采用字符串由量变向中间逐个对比的方法,而是采用计算翻转之后的数字与x是否相等的方法: class Solution: # @return a boolean d ...

  7. hangfire docker-compose 运行

    hangfire 是一款基于.net 的任务调度系统 docker-compose 文件 version: '3' services: hangfire: image: direktchark/han ...

  8. 使用Apriori进行关联分析(二)

    书接上文(使用Apriori进行关联分析(一)),介绍如何挖掘关联规则. 发现关联规则 我们的目标是通过频繁项集挖掘到隐藏的关联规则. 所谓关联规则,指通过某个元素集推导出另一个元素集.比如有一个频繁 ...

  9. position:relative与position:absolute 区别

    relative:相对于它本身原来的位置进行偏移(配合 right left bottom top属性进行偏移) 他偏移会空出来一些空白 其余的html元素不会填充这些空白 absolute:相对于同 ...

  10. 自定义django的admin后台action

    django的admin后台管理系统中自带了一个批量删除所选对象的action. 我们还可以添加自定义的action来实现其它类似的功能,如批量修改某个字段的功能.简单的,例如将文章批量标记为已发布的 ...