并发编程中的一致性问题:ForkJoinPool调用shutdown的bug.(jdk8&jdk9)
2017-09-16 15:58
916 查看
ForkJoinPool调用shutdown从而终止整个并发执行框架。
包括取消所有队列中已有的任务,终止所有的工作线程。
考虑这样一个场景,一个线程正在提交任务,另一个线程正在调用shutdown终止线程池。
此时涉及到3个独立的执行逻辑:
调用pool.submit的线程
调用pool.shutdown()的线程
线程池中的工作线程
由于这三个独立的执行逻辑,必须就线程池将(SHUTDOWN)这一状态达成共识。有没有可能会产生不一致的状态,从而导致丢失信号呢?
在当前的jdk8中的实现存在这个bug,JDK 9 Early-Access中也存在这个问题,只是重现方法不同,见下文。
我们使用如下的test case(jdk8):
再次执行,输出为:
也就是线程池,面对提交的一个任务,要么执行它,要么拒绝它,不能永远什么都不做。
我们接着在jdk8中,如下两行(2359&2224)打上断点:
然后再次注释Thread.sleep(1000)。
debug我们一开始给出的test case。
然后shutdownPool线程在2224行上挂起,printHelloWorld线程在2359行上挂起。我们先让shutdownPool通过,再让printHelloWorld线程通过。
此时会看到我们的程序没有任何输出,并且printHelloWorld线程永远挂起了。
一个解决方案是在2359行,也就是将任务放入队列之后再次检查下SHUTDOWN状态是否被设置,假如是的话尝试将任务重新删除。
jdk9中的实现与8已经不一样了。详情可见点击打开链接
包括取消所有队列中已有的任务,终止所有的工作线程。
考虑这样一个场景,一个线程正在提交任务,另一个线程正在调用shutdown终止线程池。
此时涉及到3个独立的执行逻辑:
调用pool.submit的线程
调用pool.shutdown()的线程
线程池中的工作线程
由于这三个独立的执行逻辑,必须就线程池将(SHUTDOWN)这一状态达成共识。有没有可能会产生不一致的状态,从而导致丢失信号呢?
在当前的jdk8中的实现存在这个bug,JDK 9 Early-Access中也存在这个问题,只是重现方法不同,见下文。
我们使用如下的test case(jdk8):
package com.psly; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; public class TestForkJoin { public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub ForkJoinPool pool = new ForkJoinPool(); new Thread(){ public void run(){ ForkJoinTask<String> task = pool.submit(new Callable<String>(){ public String call(){ return "Hello,world"; } }); //输出 Hello,world (永远不会输出,也不会报异常, 所以这是个bug) try { System.out.println(task.get()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } { this.setName("printHelloWorld");this.start(); } }; // Thread.sleep(1000); new Thread(){ public void run(){ pool.shutdown(); } { this.setName("shutdownPool");this.start(); } }; } }执行以上代码,输出为:
Exception in thread "printHelloWorld" java.util.concurrent.RejectedExecutionException at java.util.concurrent.ForkJoinPool.externalSubmit(ForkJoinPool.java:2328) at java.util.concurrent.ForkJoinPool.externalPush(ForkJoinPool.java:2419) at java.util.concurrent.ForkJoinPool.submit(ForkJoinPool.java:2675) at com.psly.TestForkJoin$1.run(TestForkJoin.java:14)任务被拒绝,这是一个可接受的结果。
// Thread.sleep(1000);我们去除这一句注释,让shutdown线程晚1秒一点执行。
再次执行,输出为:
Hello,world这同样是一个可以接受的结果。
也就是线程池,面对提交的一个任务,要么执行它,要么拒绝它,不能永远什么都不做。
我们接着在jdk8中,如下两行(2359&2224)打上断点:
2359 U.putOrderedObject(a, j, task); 2224 rs = lockRunState(); // enter SHUTDOWN phase
然后再次注释Thread.sleep(1000)。
// Thread.sleep(1000);
debug我们一开始给出的test case。
然后shutdownPool线程在2224行上挂起,printHelloWorld线程在2359行上挂起。我们先让shutdownPool通过,再让printHelloWorld线程通过。
此时会看到我们的程序没有任何输出,并且printHelloWorld线程永远挂起了。
一个解决方案是在2359行,也就是将任务放入队列之后再次检查下SHUTDOWN状态是否被设置,假如是的话尝试将任务重新删除。
jdk9中的实现与8已经不一样了。详情可见点击打开链接
相关文章推荐
- Java并发包:Java Fork and Join using ForkJoinPool
- 【Java并发编程】Fork/Join
- 并发编程之 Fork-Join 分而治之框架
- 并发编程之 Fork-Join 分而治之框架
- Java并发编程--Fork/Join框架使用
- Java7并发编程指南——第五章:Fork Join框架
- Java并发编程核心方法与框架-Fork-Join分治编程(一)
- Java并发编程核心方法与框架-Fork-Join分治编程(二)
- Java并发编程核心方法与框架-Fork-Join分治编程(二)
- 非主流并发工具之 ForkJoinPool
- 【Java并发编程】Fork/join 并发编程模型,让多核cpu发挥最大优势。
- 【Java并发编程的艺术】【学习笔记】ThreadLocal与Fork/Join
- 《Java 7 并发编程指南》学习概要 (6) Fork/Join
- 【Java并发编程】Fork/join 并发编程模型,让多核cpu发挥最大优势。
- Java并发编程之volatile、synchronized、yield、join
- java并发包:fork/join
- Linux下调用fork或system启动子进程的信号和资源释放相关问题
- 高并发编程-05-活跃性问题-死锁,活锁,饥饿
- Java并发编程-10-在锁中使用多条件-生产者消费者问题
- 并发基础_13_并发_框架_Fork/Join