1

我正在学习Phaser。在这样做的过程中,我遇到了一个问题。下面是我的代码,

public class RunnableTask implements Runnable {

    private Phaser phaser;

    public RunnableTask(Phaser phaser) {
        this.phaser = phaser;
        this.phaser.register();  // Question
    }

    @Override
    public void run() {
        // this.phaser.register();  // Question
        print("After register");
        for (int i = 0; i < 2; i++) {
            sleep();
            print("Before await" + i + ":");
            this.phaser.arriveAndAwaitAdvance();
            print("After advance" + i + ":");
        }
    }

    private void sleep() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void print(String msg) {

        System.out.println(String.format("%s: %s, time=%s, registered=%s, arrived=%s, unarrived=%s, phase=%s.", msg,
                Thread.currentThread().getName(), LocalTime.now(), this.phaser.getRegisteredParties(),
                this.phaser.getArrivedParties(), this.phaser.getUnarrivedParties(), this.phaser.getPhase()));
    }

 }

上述样本测试

public class TestPhaser {

    public static void main(String[] args) {

        Phaser phaser = new Phaser();

        RunnableTask task = new RunnableTask(phaser);

        Thread t1 = new Thread(task, "t1");
        Thread t2 = new Thread(task, "t2");
        Thread t3 = new Thread(task, "t3");

        t1.start();
        t2.start();
        t3.start();
    } 
}

执行上述程序后,输出为:

注册后:t3,时间=22:01:26.636,注册=1,到达=0,未到达=1,阶段=0。

注册后:t2,时间=22:01:26.636,注册=1,到达=0,未到达=1,阶段=0。

注册后:t1,时间=22:01:26.636,注册=1,到达=0,未到达=1,阶段=0。

在等待 0::t3 之前,时间=22:01:28.728,注册=1,到达=0,未到达=1,阶段=0。

在等待 0::t2 之前,时间=22:01:28.728,注册=1,到达=0,未到达=1,阶段=0。

在等待 0::t1 之前,时间=22:01:28.728,注册=1,到达=0,未到达=1,阶段=0。

在提前 0::t1 之后,时间=22:01:28.728,注册=1,到达=0,未到达=1,阶段=3。

提前0::t2后,时间=22:01:28.728,注册=1,到达=0,未到达=1,阶段=3。

在提前 0::t3 之后,时间=22:01:28.729,注册=1,到达=0,未到达=1,阶段=3。

在等待 1::t2 之前,时间=22:01:30.730,注册=1,到达=0,未到达=1,阶段=3。

在等待 1::t3 之前,时间=22:01:30.730,注册=1,到达=0,未到达=1,阶段=3。

在提前 1::t2 之后,时间=22:01:30.730,注册=1,到达=0,未到达=1,阶段=4。

在提前 1::t3 之后,时间=22:01:30.732,注册=1,到达=0,未到达=1,阶段=5。

在等待 1::t1 之前,时间=22:01:30.730,注册=1,到达=0,未到达=1,阶段=3。

在提前 1::t1 之后,时间=22:01:30.732,注册=1,到达=0,未到达=1,阶段=6。

您可以看到这里有很多差异。线程不是按顺序推进的。此外,很少有阶段丢失或/和不按顺序排列。

当我将代码 this.phaser.register() 从构造函数移动到 run 方法的开头时,输出为:

注册后:t1,时间=22:10:58.230,注册=3,到达=0,未到达=3,阶段=0。

注册后:t3,时间=22:10:58.230,注册=3,到达=0,未到达=3,阶段=0。

注册后:t2,时间=22:10:58.230,注册=3,到达=0,未到达=3,阶段=0。

在等待 0::t2 之前,时间=22:11:00.314,注册=3,到达=0,未到达=3,阶段=0。

在等待 0::t1 之前,时间=22:11:00.314,注册=3,到达=0,未到达=3,阶段=0。

在等待 0::t3 之前,时间=22:11:00.314,注册=3,到达=0,未到达=3,阶段=0。

提前0::t2后,时间=22:11:00.315,注册=3,到达=0,未到达=3,阶段=1。

提前0::t3后,时间=22:11:00.315,注册=3,到达=0,未到达=3,阶段=1。

提前0::t1后,时间=22:11:00.315,注册=3,到达=0,未到达=3,阶段=1。

在等待 1::t1 之前,时间=22:11:02.319,注册=3,到达=0,未到达=3,阶段=1。

在等待 1::t2 之前,时间=22:11:02.319,注册=3,到达=0,未到达=3,阶段=1。

在等待 1::t3 之前,时间=22:11:02.319,注册=3,到达=0,未到达=3,阶段=1。

在提前 1::t3 之后,时间=22:11:02.320,注册=3,到达=0,未到达=3,阶段=2。

在提前 1::t2 之后,时间=22:11:02.320,注册=3,到达=0,未到达=3,阶段=2。

在提前 1::t1 之后,时间=22:11:02.321,注册=3,到达=0,未到达=3,阶段=2。

这看起来好多了,线程执行和阶段是按顺序排列的。

以下是我的问题:

1) 为什么在 Runnable 的构造函数中注册方时会有很多差异?

2) 在第二个结果中,到达和未到达的统计数据在每个阶段都为零(不正确)。那么,如何为他们获取正确的数字呢?

任何帮助表示赞赏。

4

1 回答 1

1

在第一个示例“构造函数中的创建移相器”中,您仅向移相器注册了一个线程。您必须创建三个任务,才能在移相器中注册三个线程。

像这样更改代码,它将起作用。(不要忘记从您的代码中删除初始 RunnableTask task = new RunnableTask(phaser);)

    Thread t1 = new Thread(new RunnableTask(phaser), "t1");
    Thread t2 = new Thread(new RunnableTask(phaser), "t2");
    Thread t3 = new Thread(new RunnableTask(phaser), "t3");

在第二个示例中,您在所有线程中恰好等待 2 秒,这是准确的,并且所有线程几乎同时到达和等待,像这样更改您的睡眠方法以引入一些不同的等待,以便线程看到一些到达和未到达线程

  private void sleep() {
    try {
      Random r = new Random();    
      TimeUnit.SECONDS.sleep(r.nextInt(5));
    } catch(InterruptedException e) {
      e.printStackTrace();
    }
  }

您的第二个示例有效但不正确。它之所以有效,只是因为您在 run 方法的开头有睡眠,因此所有线程在您调用移相器上的到达和高级方法之前赶上在移相器中注册。如果您要删除睡眠,那么在调用此行之后

t1.start();

将运行 T1 运行方法,并且将在移相器中注册 t1 线程。那么可能会在线程 t2 和 t3 启动并在移相器中注册之前调用 t1 运行方法中的this.phaser.arriveAndAwaitAdvance(),因此移相器不会等待它们。

您应该在任务的构造函数中注册到 Phaser,或者在启动线程之前调用的方法中注册。

于 2018-11-26T09:03:17.660 回答