系列目錄
- 【詳解】ThreadPoolExecutor原始碼閱讀(一)
- 【詳解】ThreadPoolExecutor原始碼閱讀(二)
- 【詳解】ThreadPoolExecutor原始碼閱讀(三)
工作原理簡介
ThreadPoolExecutor會建立一組工作執行緒,每當一個工作執行緒完成其任務的時候,會向任務佇列獲取新的任務執行。如果任務佇列為空,獲取任務的執行緒將被阻塞。不出意外的話,工作執行緒會一直工作,直到執行緒池主動釋放空閒執行緒,或者隨著執行緒池的終結而結束。
工作者執行緒
在ThreadPoolExecutor中有一個內部類Worker,但這個Woker類並沒有像想象中的那樣繼承於Thread,而是通過組合的方式繫結一個執行緒。在一定程度上,也可以把這個Worker看作是一個工作者執行緒。
(可能是由於想要使用AbstractQueuedSynchronizer的功能吧,Java的類不支援多繼承,就只好採取組合的方式來處理了)
這個Worker如何與一個執行緒繫結?
這個工作者任務是在建立的時候與一個執行緒繫結的,其通過外部類ThreadPoolExecutor提供的執行緒工廠,建立一個執行緒,把自己傳遞給它,並保留執行緒的引用。
Worker(Runnable firstTask) { //防止在runWorker之前被中斷,因為worker一旦建立就會加入workers集合中 //其他執行緒可能會中斷空閒執行緒 //而空閒執行緒的依據就是能否獲得worker的鎖 setState(-1); //設定初始任務,注意這裡沒有null檢查,故初始任務可以為空 this.firstTask = firstTask; //通過ThreadPoolExecutor的提供執行緒工廠來建立執行緒,並把自身賦值給它,作為其執行緒任務 //保留執行緒引用,用於中斷執行緒 this.thread = getThreadFactory().newThread(this); }
Worker繫結的執行緒何時啟動?
至此,執行緒的建立和繫結完成了(這裡的執行緒指的只是Java的Thread物件),但是還沒見到執行緒的啟動(啟動後才建立OS執行緒)。因為啟動執行緒,必須通過Thread的start方法啟動。那就來找找start方法在何處呼叫。
在ThreadPoolExecutor的addWorker中,我們找到,當建立的Worker物件成功加入workers集合後,將啟動對應執行緒。
private boolean addWorker(Runnable firstTask, boolean core) { //core表示是否是核心執行緒 //先試圖改變控制資訊內 工作執行緒數 的值 retry: for (;;) { //獲得控制資訊 int c = ctl.get(); //從控制資訊內 獲取執行緒池執行狀態 int rs = runStateOf(c); //如果已經SHUTDOWN或者STOP則不再新增新工作執行緒 //除非,在SHUTDOWN狀態下,有任務尚未完成,不接受新任務 if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { //從控制資訊內獲取 工作執行緒數 int wc = workerCountOf(c); //工作執行緒以超過容量 或 //核心執行緒,超過核心執行緒數 //非核心執行緒超過最大執行緒數 //不得新增新執行緒 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; //CAS改變控制資訊內 工作執行緒數的值 +1 ,並結束自旋 if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; } } boolean workerStarted = false; //worker執行緒是否已經啟動 boolean workerAdded = false; //worker執行緒是否已加入workers集合 Worker w = null; try { w = new Worker(firstTask); //建立新執行緒,把初始任務賦值給它 final Thread t = w.thread; //獲取Worker的執行緒引用 if (t != null) { //因為要修改集合HashSet,故需獲取執行緒池的鎖,以保證執行緒安全 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //獲取鎖後再次檢查狀態,有可能在獲得鎖之前,執行緒池已經被shutdown了 int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) //提前檢查執行緒能否start throw new IllegalThreadStateException(); //把worker物件加入workers集合 workers.add(w); int s = workers.size(); //更新largetstPoolSize,此欄位表示執行緒池執行時,最多開啟過多少個執行緒 if (s > largestPoolSize) largestPoolSize = s; //執行緒已加入集合,如果前面出現異常,這裡不會被執行 workerAdded = true; } } finally { mainLock.unlock(); } //如果新增成功,則啟動執行緒 if (workerAdded) { t.start(); workerStarted = true; } } } finally { //如果啟動失敗了,則表示新增Worker失敗,回滾 if (! workerStarted) //這個方法,會把前面新增到workers集合中的對應worker刪除 //並且把前面更新的 控制資訊內的工作執行緒數再減回來 addWorkerFailed(w); } return workerStarted; }
那執行緒啟動後,將執行什麼方法呢?
那當然是執行Thread物件的run方法了,由於這裡採用的是傳遞Runnable物件的方式建立執行緒任務,故Thread的run方法執行的是其target的run方法。而這個target正是前面傳遞給它的Worker。故執行的是Worker的run方法,如下:
這裡的runWorker是其外部類ThreadPoolExecutor的方法。
final void runWorker(Worker w) { //獲得當前執行這段程式碼的執行緒 Thread wt = Thread.currentThread(); //先嚐試從worker取得初始任務 Runnable task = w.firstTask; w.firstTask = null; //允許中斷,unlock後state=1,中斷方法獲取到鎖,則判斷為空閒執行緒,可中斷 w.unlock(); boolean completedAbruptly = true; try { //不斷地取任務執行、 其中getTask提供阻塞。如果getTask返回null則退出迴圈 while (task != null || (task = getTask()) != null) { //獲取鎖,標識此執行緒正在工作,非空閒執行緒 w.lock(); if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { //鉤子函式,空實現,子類可根據需要進行實現 beforeExecute(wt, task); Throwable thrown = null; try { //執行獲取到的任務 task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { //鉤子函式 afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } //如果因為異常退出,這段語句不會被執行,也就是說completedAbruptly==true completedAbruptly = false; } finally { //工作執行緒退出的處理操作,如獲取當前worker完成的任務量 //如果異常退出,還需彌補,補充工作執行緒等等 processWorkerExit(w, completedAbruptly); } }
注:這裡還提供了beforeExecute和afterExecute兩個鉤子函式,如果子類有需要,可以覆蓋它們。在這兩個時刻做一些操作。
也就是說,每個工作者任務繫結的執行緒,執行的就是上述程式碼。那麼就會有多個執行緒訪問上述程式碼。問題來了,上述程式碼會不會出現執行緒安全問題?
執行緒安全問題多出於多個執行緒對同一資源的訪問,但是上述程式碼中,每個執行緒操作的是各自繫結的Worker。這些執行緒唯一有交集的,就是取任務操作了。但是任務已經交由BlockingQueue處理了,BlockingQueue的同步特性使得多個執行緒能夠安全地獲取任務。也就是說,不會有執行緒安全問題。
ThreadPoolExecutor與ThreadPool線上程池的實現上有何差別
注:在之前的博文【胡思亂想】JNI與執行緒池的維護 中有引用一個執行緒池的實現案例,後文就叫他ThreadPool,該案例基本實現了執行緒池的功能。但是在實際生產中,由於有更細緻的需求,執行緒池的實現也複雜的多。JDK就有執行緒池的實現,ThreadPoolExecutor。
至此,我們來對比一下ThreadPoolExecutor與ThreadPool兩個執行緒池實現的差別
ThreadPool中,工作者執行緒完成手頭任務後,是迴歸到執行緒池,等待ThreadPool給它分配任務。(ThreadPool是一個執行緒類),也就是說在ThreadPool的實現中執行緒池還有一個執行緒用來分發任務。
ThreadPoolExecutor中,工作者執行緒一旦完成手頭的任務,就自行從佇列中獲取新的任務接著做。如果沒有任務,將被阻塞,其執行緒池把任務分發(可能需要的同步,阻塞)的責任剝離了出來,交由BlockingQueue進行處理。