ForkJoinPool、Phaser 和托管阻塞:它们在多大程度上可以防止死锁?
ForkJoinPool, Phaser and managed blocking: to what extent do they works against deadlocks?
这个小代码片段永远不会在 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;
}
}
}
Phasers may also be used by tasks executing in a ForkJoinPool, which will ensure sufficient parallelism to execute tasks when others are blocked waiting for a phase to advance.
但是 javadoc of ForkjoinPool#mangedBlock 指出:
If running in a ForkJoinPool, the pool may first be expanded to ensure sufficient parallelism
只有一个可能。所以我不确定这是否是一个错误,或者只是不依赖于 Phaser/ForkJoinPool 契约的错误代码:组合 Phaser/ForkJoinPool 的契约在防止死锁方面有多难?
我的配置:
- Linux adc 3.14.27-100.fc19.x86_64 #1 SMP Wed Dec 17 19:36:34 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
- 8 核 i7
看来您的问题是由于 JDK 8u20 和 8u45 之间的 ForkJoinPool 代码发生了变化。
在 u20 中,ForkJoin 线程在被回收之前总是至少存活 200 毫秒(参见 ForkJoinPool.FAST_IDLE_TIMEOUT)。
在 u45 中,一旦 ForkJoinPool 达到其目标并行度加上 2 个额外线程,线程将在 运行 停止工作后立即死亡,无需等待。
您可以在 ForkJoinPool.java(第 1810 行)中的 awaitWork 方法中看到此更改:
int t = (short)(c >>> TC_SHIFT); // shrink excess spares
if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl))
return false;
您的程序使用Phasers 任务来创建额外的worker。每个任务都会产生一个新的补偿工作人员,该工作人员旨在接手下一个提交的任务。
但是,一旦达到目标并行度 + 2,补偿 worker 会立即死亡而不会等待,并且没有机会接手之后将立即提交的任务。
希望对您有所帮助。
这个小代码片段永远不会在 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;
}
}
}
Phasers may also be used by tasks executing in a ForkJoinPool, which will ensure sufficient parallelism to execute tasks when others are blocked waiting for a phase to advance.
但是 javadoc of ForkjoinPool#mangedBlock 指出:
If running in a ForkJoinPool, the pool may first be expanded to ensure sufficient parallelism
只有一个可能。所以我不确定这是否是一个错误,或者只是不依赖于 Phaser/ForkJoinPool 契约的错误代码:组合 Phaser/ForkJoinPool 的契约在防止死锁方面有多难?
我的配置:
- Linux adc 3.14.27-100.fc19.x86_64 #1 SMP Wed Dec 17 19:36:34 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
- 8 核 i7
看来您的问题是由于 JDK 8u20 和 8u45 之间的 ForkJoinPool 代码发生了变化。
在 u20 中,ForkJoin 线程在被回收之前总是至少存活 200 毫秒(参见 ForkJoinPool.FAST_IDLE_TIMEOUT)。
在 u45 中,一旦 ForkJoinPool 达到其目标并行度加上 2 个额外线程,线程将在 运行 停止工作后立即死亡,无需等待。 您可以在 ForkJoinPool.java(第 1810 行)中的 awaitWork 方法中看到此更改:
int t = (short)(c >>> TC_SHIFT); // shrink excess spares
if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl))
return false;
您的程序使用Phasers 任务来创建额外的worker。每个任务都会产生一个新的补偿工作人员,该工作人员旨在接手下一个提交的任务。
但是,一旦达到目标并行度 + 2,补偿 worker 会立即死亡而不会等待,并且没有机会接手之后将立即提交的任务。
希望对您有所帮助。