ForkJoinPool,Phaser和托管阻塞:它们在多大程度上对抗死锁?

时间:2015-05-22 09:12:24

标签: java java-8 phaser forkjoinpool

这个小代码片段永远不会在jdk8u45上完成,并且用于在jdk8u20上正确完成:

public class TestForkJoinPool {

    final static ExecutorService pool = Executors.newWorkStealingPool(8);
    private static volatile long consumedCPU = System.nanoTime();

    public static void main(String[] args) throws InterruptedException {
        final int numParties = 100;
        final Phaser p = new Phaser(1);
        final Runnable r = () -> {
            p.register();
            p.arriveAndAwaitAdvance();
            p.arriveAndDeregister();
        };

        for (int i = 0; i < numParties; ++i) {
            consumeCPU(1000000);
            pool.submit(r);
        }

        while (p.getArrivedParties() != numParties) {}
    }

    static void consumeCPU(long tokens) {
        // Taken from JMH blackhole
        long t = consumedCPU;
        for (long i = tokens; i > 0; i--) {
            t += (t * 0x5DEECE66DL + 0xBL + i) & (0xFFFFFFFFFFFFL);
        }
        if (t == 42) {
            consumedCPU += t;
        }
    }
}

doc of phaser表示

  

在ForkJoinPool中执行的任务也可以使用Phasers,这将确保在其他人被阻止等待阶段前进时执行任务时有足够的并行性。

然而javadoc of ForkjoinPool#mangedBlock表示:

  

如果在ForkJoinPool中运行,可以首先扩展池以确保足够的并行性

只有一个可能在那里。所以我不确定这是不是一个bug,或者只是不依赖Phaser / ForkJoinPool合同的坏代码:Phaser / ForkJoinPool组合的合同有多难以防止死锁?

我的配置:

  1. Linux adc 3.14.27-100.fc19.x86_64#1 SMP Wed Dec 17 17:36:34 UTC 2014 x86_64 x86_64 x86_64 GNU / Linux
  2. 8核i7

1 个答案:

答案 0 :(得分:1)

看起来您的问题来自于JDK 8u20和8u45之间的ForkJoinPool代码的更改。

在u20中,ForkJoin线程在被回收之前总是存活至少200毫秒(参见ForkJoinPool.FAST_IDLE_TIMEOUT)。

在u45中,一旦ForkJoinPool达到其目标并行度加上2个额外线程,线程将在它们无法运行时立即死亡。 您可以在ForkJoinPool.java中的awaitWork方法中看到此更改(第1810行):

    int t = (short)(c >>> TC_SHIFT);  // shrink excess spares
    if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl))
        return false; 

您的程序使用Phasers任务来创建额外的工作人员。每个任务都会生成一个新的补偿工作程序,用于获取下一个提交的任务。
但是,一旦达到目标并行度+ 2,补偿工作者就会立即死亡而无需等待,并且没有机会接受将在之后立即提交的任务。

我希望这会有所帮助。