在本文中,我们将从java.util.concurrent包中查看Phaser 构造。它与CountDownLatch非常相似,允许我们协调线程的执行。与CountDownLatch相比,它具有一些额外的功能。
Phaser是在线程动态数需要继续执行之前等待的屏障。在CountDownLatch中,该数字无法动态配置,需要在创建实例时提供。
该Phaser使我们能够建立在逻辑线程需要才去执行下一步的障碍等。
我们可以协调多个执行阶段,为每个程序阶段重用Phaser实例。每个阶段可以有不同数量的线程等待前进到另一个阶段。我们稍后会看一个使用阶段的示例。
要参与协调,线程需要使用Phaser实例 register() 本身。请注意:这只会增加注册方的数量,我们无法检查当前线程是否已注册 - 我们必须将实现子类化以支持此操作。
线程通过调用 arriAndAwaitAdvance() 来阻止它到达屏障,这是一种阻塞方法。当数量到达等于注册的数量时,程序的执行将继续,并且数量将增加。我们可以通过调用getPhase()方法获取当前数量。
当线程完成其工作时,我们应该调用arrivalAndDeregister()方法来表示在此特定阶段不再考虑当前线程。
假设我们想要协调多个行动阶段。三个线程将处理第一个阶段,两个线程将处理第二个阶段。
我们将创建一个实现Runnable接口的LongRunningAction类:
class LongRunningAction implements Runnable {
private String threadName;
private Phaser ph;
LongRunningAction(String threadName, Phaser ph) {
this.threadName = threadName;
this.ph = ph;
ph.register();
}
@Override
public void run() {
ph.arriveAndAwaitAdvance();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
ph.arriveAndDeregister();
}
}
当我们的动作类被实例化时,我们使用register()方法注册到Phaser实例。这将增加使用该特定Phaser的线程数。
对arrivalAndAwaitAdvance()的调用将导致当前线程在屏障上等待。如已经提到的,当到达的数量变得与注册的数量相同时,执行将继续。
处理完成后,当前线程通过调用arrivalAndDeregister()方法取消注册。
让我们创建一个测试用例,在其中我们将启动三个LongRunningAction线程并阻塞屏障。接下来,在操作完成后,我们将创建另外两个LongRunningAction线程,这些线程将执行下一阶段的处理。
从主线程创建Phaser实例时,我们将1作为参数传递。这相当于从当前线程调用register()方法。我们这样做是因为,当我们创建三个工作线程时,主线程是一个协调器,因此Phaser需要注册四个线程:
ExecutorService executorService = Executors.newCachedThreadPool();
Phaser ph = new Phaser(1);
assertEquals(0, ph.getPhase());
初始化后的相位等于零。
该移相器类有一个构造函数中,我们可以在父实例传递给它。在我们有大量参与者会遇到大量同步争用成本的情况下,它非常有用。在这种情况下,可以建立Phasers的实例,以便子组的子组共享一个共同的父组。
接下来,让我们启动三个LongRunningAction操作线程,它将在屏障上等待,直到我们从主线程调用arrivalAndAwaitAdvance()方法。
请记住,我们已经用1初始化了Phaser,并且多次调用了register()三次。现在,三个动作线程已经宣布它们已经到达障碍,因此需要再次调用arriAndAwaitAdvance() - 来自主线程的一个:
executorService.submit(new LongRunningAction("thread-1", ph));
executorService.submit(new LongRunningAction("thread-2", ph));
executorService.submit(new LongRunningAction("thread-3", ph));
ph.arriveAndAwaitAdvance();
assertEquals(1, ph.getPhase());
在该阶段完成之后,getPhase()方法将返回一个,因为程序已完成处理第一步执行。
假设两个线程应该进行下一个处理阶段。我们可以利用Phaser实现这一点,因为它允许我们动态配置应该在屏障上等待的线程数。我们正在启动两个新线程,但是直到从主线程调用arrivalAndAwaitAdvance()之后才会执行这些线程(与前一个案例相同):
executorService.submit(new LongRunningAction("thread-4", ph));
executorService.submit(new LongRunningAction("thread-5", ph));
ph.arriveAndAwaitAdvance();
assertEquals(2, ph.getPhase());
ph.arriveAndDeregister();
在此之后,getPhase()方法将返回相位数等于2。当我们想要完成我们的程序时,我们需要调用arriAndDeregister()方法,因为主线程仍然在Phaser中注册。当注销导致注册政党的数量为零时,移相器被终止。对同步方法的所有调用都不会再阻塞,并将立即返回。
运行该程序将产生以下输出(可在代码存储库中找到带有打印行语句的完整源代码):
This is phase 0
This is phase 0
This is phase 0
Thread thread-2 before long running action
Thread thread-1 before long running action
Thread thread-3 before long running action
This is phase 1
This is phase 1
Thread thread-4 before long running action
Thread thread-5 before long running action
我们看到所有线程都在等待执行,直到屏障打开。仅当前一个成功完成时才执行下一个执行阶段。
我们从java.util.concurrent看了Phaser构造,并使用Phaser类实现了多个阶段的协调逻辑。
关注公众号:「Java知己」,每天更新Java知识哦,期待你的到来!