併發編程中的一致性問題:ForkJoinPool調用shutdown的bug.(jdk8&jdk9)

ForkJoinPool調用shutdown從而終止整個併發執行框架。

包括取消所有隊列中已有的任務,終止所有的工作線程。

考慮這樣一個場景,一個線程正在提交任務,另一個線程正在調用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已經不一樣了。詳情可見點擊打開鏈接


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章