フェイザーコンカレントフェイザー

フェイザーコンカレントフェイザー

PhaserはJDK1.7によって提案されました。これは、複雑で強力な同期補助クラスです。同期ツールクラスCountDownLatchおよびCyclicBarrierの包括的なアップグレードであり、段階的に待機を実装するビジネスシナリオをサポートできます。

CountDownLatchが最初にNスレッドを指定することについて話していることを思い出すことができます。Nスレッドが作業を完了する前に、他のスレッドは待機する必要があります(ツアーガイドは、運転する前にツアーグループの全員が車に乗るのを待ちます)。最初にスレッド。スレッド。Nスレッドが到着すると、全員が同時に作業し(複数のロバの友人が集まって旅行し、最初の友人は後で待つ必要があります)、Phaserは2つの組み合わせであり、最初にNスレッドを指定すると理解できます。 Nスレッドが到着するのを待っています。結局、第1ステージの作業を開始し、第1ステージのすべてのスレッドが作業を終了するのを待ってから、Nスレッドが第2ステージの作業を開始します。ステージが完了し、プログラムが終了します。もちろん、各ステージはビジネスニーズに応じて一部のスレッドを追加または削除できることに注意してください。各ステージに含めるスレッドの数を指定する必要はありません。

入門

コンセプトを読んだ後は、理解するのは簡単ではないかもしれません。小さなデモから始めて体験してください

public class PhaserDemo1 {
    // 指定随机种子
    private static Random random = new Random(System.currentTimeMillis());

    public static void main(String[] args) {
        Phaser phaser = new Phaser();
        // 将线程注册到phaser
        phaser.register();
        for (int i = 0; i <5 ; i++) {
            Task task = new Task(phaser);
            task.start();
        }
        phaser.arriveAndAwaitAdvance();
        System.out.println("all task execute close");
    }

    static class Task extends Thread{
        Phaser phaser;

        public Task(Phaser phaser){
            this.phaser = phaser;
            this.phaser.register();
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName()+"开始执行");
                TimeUnit.SECONDS.sleep(random.nextInt(5));
                System.out.println(Thread.currentThread().getName()+"执行完毕");
                // 类似CountDownLatch中的 await
                phaser.arriveAndAwaitAdvance();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

复制代码

そのような疑問があるかどうかはわかりませんが、phaser.registerはこのスレッドをphaserに登録することですが、なぜメインスレッドも登録する必要があるのですか?

実際、メインスレッドは実行を続行する前にすべてのサブスレッドが完了するのを待つ必要があるので、phaser.arriveAndAwaitAdvance();が待機をブロックする必要があります。このステートメントは、現在のスレッドがバリアであり、条件が満たされた後、しばらくここで待機する必要があります。次のバリアに進みます。メインスレッドのphaser.registerがない場合は、phaser.arriveAndAwaitAdvanceを直接呼び出します。これはソースコードに記載されています。例外がある可能性があるため、phaser.register()をメインプログラムに登録する必要があります。

/* <p>It is a usage error for an unregistered party to invoke this
* method.  However, this error may result in an {@code
* IllegalStateException} only upon some subsequent operation on
* this phaser, if ever.
*/
译:
未注册方调用此函数是一个使用错误方法。但是,这个错误可能会导致
{@codeIllegalStateException}仅在一些后续操作这个相位器,如果有的话。

复制代码

Phaserはサブテストの問題を解決します

体験の例からは、そのメリットがどこにあるのかよくわかりません。アピールシーンではCountDownLatchを完全に使用できるので、シーンを変更してPhaserのメリットを説明しましょう。

学校が最終試験を実施するとします。試験は中国語、数学、英語の3つです。各コースの事前に書類を提出することができます。次の試験は、すべての学生が試験を完了した後にのみ実施できます。これは典型的な段階的タスク処理例は次のとおりです。

图片

次のようなセマンティックアピールシーン

public class PhaserExam {
    public static Random random = new Random(System.currentTimeMillis());
    public static void main(String[] args) {
        // 一次初始化2个 相当于两次register
        Phaser phaser = new Phaser(2);

        for (int i = 0; i <2 ; i++) {
            Exam exam = new Exam(phaser,random.nextLong());
            exam.start();
        }
    }

    static class Exam extends Thread{
        Phaser phaser;
        Long id;
        public Exam(Phaser phaser,Long id){
            this.phaser = phaser;
            this.id = id;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName()+"===开始语文考试");
                TimeUnit.SECONDS.sleep(random.nextInt(5));
                System.out.println(Thread.currentThread().getName()+"===结束语文考试");
                phaser.arriveAndAwaitAdvance();

                System.out.println(Thread.currentThread().getName()+"===开始数学考试");
                TimeUnit.SECONDS.sleep(random.nextInt(5));
                System.out.println(Thread.currentThread().getName()+"===结束数学考试");
                phaser.arriveAndAwaitAdvance();

                System.out.println(Thread.currentThread().getName()+"===开始英语考试");
                TimeUnit.SECONDS.sleep(random.nextInt(5));
                System.out.println(Thread.currentThread().getName()+"===结束英语考试");
                phaser.arriveAndAwaitAdvance();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

复制代码

代码执行结果如下,可以看到三个阶段都是等待所有线程执行完毕后才往下执行,相当于多个栅栏。图片

到这里请注意,通过Phaser类的构造方法构建的party数,也就是线程数需要和循环的次数对应,不然可能影响后续阶段器的正常运行。

两个重要状态

在Phaser内有2个重要状态,分别是phase和party,乍一看很难理解,他们的定义如下。

phase就是阶段,如上面提到的语文、数学、英语考试这每个考试对应一个阶段,不过phase是从0开始的,当所有任务执行完毕,准备进入下一个阶段时phase就会加一。

party对应注册到Phaser线程数,party初始值有两种形式

  • 方法一就是通过Phaser的有参构造初始化party值。

  • 方法二采用动态注册方法phaser.register()或phaser.bulkRegister(线程数)指定线程数,注销线程调用phaser.arriveAndDeregister()方法party值会减一。

Phaser常用API

Phaser常用API总结如下所示

// 获取Phaser阶段数,默认0
public final int getPhase();
// 向Phaser注册一个线程
public int register();    
// 向Phaser注册多个线程
public int bulkRegister(int parties);
// 获取已经注册的线程数,也就是重要状态party的值
public int getRegisteredParties();
// 到达并且等待其它线程到达
public int arriveAndAwaitAdvance();
// 到达后注销不等待其它线程,继续往下执行
public int arriveAndDeregister();
// 已到达线程数
public int getArrivedParties();
// 未到达线程数
public int getUnarrivedParties();
// Phaser是否结束 只有当party的数量是0或者调用方法forceTermination时才会结束
public boolean isTerminated();
// 结束Phaser
public void forceTermination();

复制代码

代码演示如下

public class PhaserApiTest {
    public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new Phaser(5);
        System.out.println("当前阶段"+phaser.getPhase());

        System.out.println("注册线程数==="+phaser.getRegisteredParties());
        // 向phaser注册一个线程
        phaser.register();
        System.out.println("注册线程数==="+phaser.getRegisteredParties());
        // 向phaser注册多个线程,批量注册
        phaser.bulkRegister(4);
        System.out.println("注册线程数==="+phaser.getRegisteredParties());

        new Thread(()->{
            // 到达且等待
            phaser.arriveAndAwaitAdvance();
            System.out.println(Thread.currentThread().getName()+"===执行1");
        }).start();

        new Thread(()->{
            // 到达不等待,从phaser中注销一个线程
            phaser.arriveAndDeregister();
            System.out.println(Thread.currentThread().getName()+"===执行2");
        }).start();

        TimeUnit.SECONDS.sleep(3);

        System.out.println("已到达线程数==="+phaser.getArrivedParties());
        System.out.println("未到达线程数==="+phaser.getUnarrivedParties());

        System.out.println("Phaser是否结束"+phaser.isTerminated());
        phaser.forceTermination();
        System.out.println("Phaser是否结束"+phaser.isTerminated());
    }
}

复制代码

执行结果如下所示图片

arriveAndAwaitAdvance解析

arriveAndAwaitAdvance是Phaser中一个重要实现阻塞的API,其实arriveAndAwaitAdvance是由arrive方法和awaitAdvance方法合并而来,两个方法的作用分别为

  • arrive:到达屏障但不阻塞,返回值为到达的阶段号。

  • awaitAdvance(int):接收一个 int 值的阶段号,在指定的屏障处阻塞。

测试代码如下

public class PhaserTestArrive {
    public static Random random = new Random(System.currentTimeMillis());
    public static void main(String[] args) {
        Phaser phaser = new Phaser(5);
        for (int i = 0; i <5 ; i++) {
            new Task(i,phaser).start();
        }
        phaser.register();
        // 主线程需要调用arrive的原因是主线程注册的第六个线程还未到达,需要手动到达,才能调用awaitAdvance阻塞屏障
        phaser.arrive();
        // 因为Phaser线程数为6,所以即使5个线程已经到达,但是还差主线程的一个,目前阶段数就是0
        phaser.awaitAdvance(0);
        System.out.println("all task is end");
    }

    static class Task extends Thread{
        Phaser phaser;

        public Task(int num,Phaser phaser){
            super("Thread--"+String.valueOf(num));
            this.phaser = phaser;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName()+"===task1 is start");
                TimeUnit.SECONDS.sleep(random.nextInt(3));
                System.out.println(Thread.currentThread().getName()+"===task1 is end");

                // 到达且不等待
                phaser.arrive();

                System.out.println(Thread.currentThread().getName()+"===task2 is start");
                TimeUnit.SECONDS.sleep(random.nextInt(3));
                System.out.println(Thread.currentThread().getName()+"===task2 is end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


复制代码

中断响应

我们需要特别注意的就是Phaser所有API中只有awaitAdvanceInterruptibly是响应中断的,其余全部不会响应中断所以不需要对其进行异常处理,演示如下

public static void main(String[] args) {
        Phaser phaser = new Phaser(3);

        Thread T1 = new Thread(()->{
            try {
                phaser.awaitAdvanceInterruptibly(phaser.getPhase());
            } catch (InterruptedException e) {
                System.out.println("中断异常");
                e.printStackTrace();
            }
            //phaser.arriveAndAwaitAdvance();
        });

        T1.start();

        T1.interrupt();

        phaser.arriveAndAwaitAdvance();
    }

复制代码

图片

本文使用 文章同步助手 同步

おすすめ

転載: juejin.im/post/7079436934707167239