你知道Java中Phaser干什么的吗?

1.概述

在本文中,我们将从java.util.concurrent包中查看Phaser 构造。它与CountDownLatch非常相似,允许我们协调线程的执行。与CountDownLatch相比,它具有一些额外的功能。

Phaser是在线程动态数需要继续执行之前等待的屏障。在CountDownLatch中,该数字无法动态配置,需要在创建实例时提供。

2. Phaser API

该Phaser使我们能够建立在逻辑线程需要才去执行下一步的障碍等。

我们可以协调多个执行阶段,为每个程序阶段重用Phaser实例。每个阶段可以有不同数量的线程等待前进到另一个阶段。我们稍后会看一个使用阶段的示例。

要参与协调,线程需要使用Phaser实例 register() 本身。请注意:这只会增加注册方的数量,我们无法检查当前线程是否已注册 - 我们必须将实现子类化以支持此操作。

线程通过调用 arriAndAwaitAdvance() 来阻止它到达屏障,这是一种阻塞方法。当数量到达等于注册的数量时,程序的执行将继续,并且数量将增加。我们可以通过调用getPhase()方法获取当前数量。

当线程完成其工作时,我们应该调用arrivalAndDeregister()方法来表示在此特定阶段不再考虑当前线程。

3.使用Phaser API 实现逻辑

假设我们想要协调多个行动阶段。三个线程将处理第一个阶段,两个线程将处理第二个阶段。

我们将创建一个实现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

我们看到所有线程都在等待执行,直到屏障打开。仅当前一个成功完成时才执行下一个执行阶段。

4.结论

我们从java.util.concurrent看了Phaser构造,并使用Phaser类实现了多个阶段的协调逻辑。

image

欢迎大家关注公众号:「Java知己」,关注公众号,回复「1024」你懂得,免费领取 30 本经典编程书籍。关注我,与 10 万程序员一起进步。 每天更新Java知识哦,期待你的到来!

image

猜你喜欢

转载自blog.csdn.net/feilang00/article/details/87974491