1、線程
- 線程是CPU 調度的最小操做單位,線程模型分爲KLT 模型和ULT 模型,JVM 使用的是KLT 模型。
- 線程的狀態 :NEW,RUNNABLE,BLOCKED,TERMINATED
2、線程池
1. 線程池解決的兩大核心問題:
- 在執行大量異步運算的時候,線程池用優化系統性能,減小線程的反覆建立所帶來的的系統開銷
- 提供了一種限制和管理資源的方法
2. 7 大核心參數:
-
corePoolSize :線程池中的核心線程數,當提交一個任務時,線程池建立一個新線程執行任務,直到當前線程數等於corePoolSize;若是當前線程數爲corePoolSize,繼續提交的任務被保存到 阻塞隊列中,等待被執行;若是執行了線程池的prestartAllCoreThreads()方法,線程池會 提早建立並啓動全部核心線程。編程
-
maximumPoolSize :線程池中容許的最大線程數。若是當前阻塞隊列滿了,且繼續提交任務,則建立新的線程執行任務,前提是當前線程數小於maximumPoolSize;數組
-
keepAliveTime :線程池維護線程所容許的空閒時間。當線程池中的線程數量大corePoolSize的時 候,若是這時沒有新的任務提交,核心線程外的線程不會當即銷燬,而是會等待,直到等待 的時間超過了keepAliveTime;併發
-
unit: keepAliveTime的單位;異步
-
workQueue: 用來保存等待被執行的任務的阻塞隊列,且任務必須實現Runable接口,在JDK中提供了以下阻塞隊列:函數
- ArrayBlockingQueue:基於數組結構的有界阻塞隊列,按FIFO排序任務;
- LinkedBlockingQuene:基於鏈表結構的阻塞隊列,按FIFO排序任務,吞吐量一般要高於ArrayBlockingQuene; -
- SynchronousQuene:一個不存儲元素的阻塞隊列,每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於 LinkedBlockingQuene;
- priorityBlockingQuene:具備優先級的無界阻塞隊列;
-
threadFactory:它是ThreadFactory類型的變量,用來建立新線程。默認使用 Executors.defaultThreadFactory() 來建立線程。使用默認ThreadFactory來建立線程 時,會使新建立的線程具備相同的NORM_PRIORITY優先級而且是非守護線程,同時也設 置了線程的名稱。oop
-
handler: 線程池的飽和策略,當阻塞隊列滿了,且沒有空閒的工做線程,若是繼續提交任務,必 須採起一種策略處理該任務,線程池提供了4種策略:性能
- AbortPolicy:直接拋出異常,默認策略;
- CallerRunsPolicy:用調用者所在的線程來執行任務;
- DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;
- DiscardPolicy:直接丟棄任務;
上面的4種策略都是ThreadPoolExecutor的內部類。固然也能夠根據應用場景實現RejectedExecutionHandler接口,自定義飽和策略,如 記錄日誌或持久化存儲不能處理的任務。測試
3. 線程池的生命週期狀態 :
- NEW
- RUNNABLE
- WATING
- BLOCKED
- TIMED_WATING
- TERMINATED
4. 線程池的重要屬性 :ctl
-
ctl 是對線程池的運行狀態和線程池中有效線程的數量進行控制的一個字段, 它包含兩 部分的信息: 線程池的運行狀態 (runState) 和線程池內有效線程的數量 (workerCount),這 裏能夠看到,使用了Integer類型來保存,高3位保存runState,低29位保存 workerCount。COUNT_BITS 就是29,CAPACITY就是1左移29位減1(29個1),這個常 量表示workerCount的上限值,大約是5億。優化
-
runState 主要提供線程池生命週期的控制,主要值包括:ui
- RUNNING
(1) 狀態說明:線程池處在RUNNING狀態時,可以接收新任務,以及對已添加的任務進行 處理。
(2) 狀態切換:線程池的初始化狀態是RUNNING。換句話說,線程池被一旦被建立,就處 於RUNNING狀態,而且線程池中的任務數爲0!
- SHUTDOWN
(1) 狀態說明:線程池處在SHUTDOWN狀態時,不接收新任務,但能處理已添加的任務。
(2) 狀態切換:調用線程池的shutdown()接口時,線程池由RUNNING -> -SHUTDOWN。
- STOP
(1) 狀態說明:線程池處在STOP狀態時,不接收新任務,不處理已添加的任務,而且會中 斷正在處理的任務。
(2) 狀態切換:調用線程池的shutdownNow()接口時,線程池由(RUNNING or SHUTDOWN ) -> STOP。
- TIDYING
(1) 狀態說明:當全部的任務已終止,ctl記錄的」任務數量」爲0,線程池會變爲TIDYING 狀態。當線程池變爲TIDYING狀態時,會執行鉤子函數terminated()。terminated()在 ThreadPoolExecutor類中是空的,若用戶想在線程池變爲TIDYING時,進行相應的處理; 能夠經過重載terminated()函數來實現。
(2) 狀態切換:當線程池在SHUTDOWN狀態下,阻塞隊列爲空而且線程池中執行的任務也 爲空時,就會由 SHUTDOWN -> TIDYING。 當線程池在STOP狀態下,線程池中執行的 任務爲空時,就會由STOP -> TIDYING。
- TERMINATED
(1) 狀態說明:線程池完全終止,就變成TERMINATED狀態。
(2) 狀態切換:線程池處在TIDYING狀態時,執行完terminated()以後,就會由 TIDYING - > TERMINATED。 進入TERMINATED的條件以下: 線程池不是RUNNING狀態; 線程池狀態不是TIDYING狀態或TERMINATED狀態; 若是線程池狀態是SHUTDOWN而且workerQueue爲空; workerCount爲0,設置TIDYING狀態成功。
- ctl相關 API
-
runStateOf():獲取運行狀態;
-
workerCountOf():獲取活動線程數;
-
ctlOf():獲取運行狀態和活動線程數的值。
5. 線程池的行爲
-
execute(Runnable command):執行Ruannable類型的任務
-
submit(task):可用來提交Callable或Runnable任務,並返回表明此任務的Future 對象
-
shutdown():在完成已提交的任務後封閉辦事,再也不接管新任務,
-
shutdownNow():中止全部正在履行的任務並封閉辦事。
-
isTerminated():測試是否全部任務都履行完畢了。
-
isShutdown():測試是否該ExecutorService已被關閉。
6. 經常使用線程池的具體實現
ThreadPoolExecutor 默認線程池 ScheduledThreadPoolExecutor 定時線程池
7. 線程池監控API
-
public long getTaskCount() //線程池已執行與未執行的任務總數
-
public long getCompletedTaskCount() //已完成的任務數
-
public int getPoolSize() //線程池當前的線程數
-
public int getActiveCount() //線程池中正在執行任務的線程數量
3、源碼解析
execute() 方法
//在未來的某個時間執行給定的任務。任務能夠是新起一個新線程或者複用現有池線程中的線程去執行 public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * 執行過程仍是分爲 3 步: * * 1.執行任務: * 若是小於核心線程數,嘗試建立一個新線程來執行給定的任務。 * 方法 addWorker() 就是真正的建立一個新線程來執行任務的方法。 * addWorker()方法會對 runState 和 workerCount進行原子檢查。 * addWorker()方法會返回一個 boolean 值,經過返回 false 值來防止在不該該添加線程的狀況下發出錯誤警報 * * * 2.添加到阻塞隊列: * 未能知足條件執行完步驟 1 則添加到阻塞隊列。 * 若是任務能夠成功排隊,會再次進行檢查,檢查是否應該添加線程(由於現有線程自上次檢查後就死了), * 或者自進入此方法以來該池已關閉。所以,須要從新檢查狀態,並在中止的狀況下在必要時回滾隊列,若是沒有,則啓動一個新線程。 * * 3.拒絕任務: * 若是沒法將任務添加至阻塞隊列,最大線程數也未達到最大會嘗試添加一個新的線程。若是失敗,說明線程池已關閉或處於飽和狀態,所以拒絕該任務。 */ //clt記錄着runState和workerCount int c = ctl.get(); /* * workerCountOf方法取出低29位的值,表示當前活動的線程數; * 若是當前活動線程數小於corePoolSize,則新建一個線程放入線程池中;並把任務添加到該線程中。 */ if (workerCountOf(c) < corePoolSize) { /* * addWorker中的第二個參數表示限制添加線程的數量是根據corePoolSize來判斷仍是maximumPoolSize來判斷 * 若是爲true,根據corePoolSize來判斷; * 若是爲false,則根據maximumPoolSize來判斷 */ if (addWorker(command, true)) return; //若是添加失敗,則從新獲取ctl值 c = ctl.get(); } //執行到此處說明從核心線程裏給當前任務分配線程失敗 //若是當前線程池是運行狀態而且任務添加到隊列成功 if (isRunning(c) && workQueue.offer(command)) { //從新獲取ctl值。即便添加隊列成功也要再次檢查,若是不是運行狀態,因爲以前已經把任務添加到workerQueue 中了,因此要移除該任務,執行事後經過handler使用拒絕策略對該任務進行處理,整個方法返回 int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); /* * 獲取線程池中的有效線程數,若是數量是0,則執行addWorker方法 這裏傳入的參數表示: * 第一個參數爲null,表示在線程池中建立一個線程,但不去啓動; * 第二個參數爲false,將線程池的有限線程數量的上限設置爲maximumPolSize,添加線程時根據maximumPoolSize來判斷; * 若是判斷workerCount大於0,則直接返回,在workQueue中新增的comman 會在未來的某個時刻被執行。 */ //由於 任務已經被添加到workQueue中了,因此worker在執行的時候,會直接從workQueue中 獲取任務。 else if (workerCountOf(recheck) == 0) //執行到這裏說明任務已經添加到阻塞隊列裏了,最大線程數也未飽和,則建立一個新的線程去阻塞隊列裏拿任務 //這步操做也就是建立一個線程,但並無傳入任務,由於任務已經被添加到workQueue中了,因此worker在執行的時候,會直接從workQueue中 獲取任務。 //爲何要這樣作呢?是爲了保證線程池在RUNNING狀態下必需要有一個線程來執行任務。 addWorker(null, false); } /* * 若是執行到這裏,有兩種狀況: * 1. 線程池已經不是RUNNING狀態; * 2. 線程池是RUNNING狀態,但workerCount >= corePoolSize而且workQueue已滿。 * 這時,再次調用addWorker方法,但第二個參數傳入爲false,將線程池的 有限線程數量的上限設置爲maximumPoolSize; * 若是失敗則拒絕該任務 */ else if (!addWorker(command, false)) reject(command); }
addWorker() 方法
/** * 檢查是否能夠根據當前線程池的狀態添加一個新的工做線程去執行任務。 * addWorker(runnable,true)表示從核心工做線程數中分配線程執行傳進來的任務; * addWorker(null,false)表示從最大線程數中分配線程執行阻塞隊列中的任務。 * 線程池若是中止或者關閉則直接返回 false,若是線程池建立新線程失敗一樣也會返回 false。 * 若是建立線程失敗,或者線程工廠返回 null,或者執行當前 addWorker()的線程拋出異常,(注意是當前線程拋出異常,當前線程拋出異常只與當前任務有關,並不影響其餘任務的執行),線程池的相關屬性會當即回滾 */ private boolean addWorker(Runnable firstTask, boolean core) { retry: // 外層 for 循環就是爲了能給任務分配線程作準備,判斷狀態--> 原子遞增 workerCount // 直到線程池狀態不符合條件返回 false ,或者自增成功跳出 for 循環 // 一樣的,getTask()從阻塞隊列中獲取任務的時候也是這麼個邏輯,先對 workerCount 原子遞減,再去執行任務 for (;;) { //能夠看到,每一步操做都會對線程池的狀態參數作判斷 int c = ctl.get(); int rs = runStateOf(c); //也是對線程池狀態,隊列狀態作檢查 /** * 這裏的狀態判斷也很好理解: * 線程池狀態爲SHUTDOWN,不會再接受新的任務了,返回 false * 想城池狀態不爲SHUTDOWN,傳進來的任務爲空,而且阻塞隊列裏也沒任務,那還執行個錘子任務,一樣返回 false */ if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; //前面已經判斷過知足爲任務分配一個線程去執行任務 //這個 for 循環就是爲了建立任務作準備,先去原子性的遞增 workerCount,workerCount 遞增成功了纔會去真正的爲任務分配線程去執行 for (;;) { //當前工做線程數 int wc = workerCountOf(c); //當前工做線程數大於corePoolSize 或者 maximumPoolSize (跟誰比較就是根據傳進來的參數 core 判斷), //說明也沒有分配的線程能夠執行任務了,返回 false if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; /** * 執行到這裏說明知足條件了,能夠分配出來線程去執行任務了 * 嘗試增長workerCount,若是成功,則跳出第一個for循環 * 這裏是進行 CAS 自增 ctl 的 workerCount(先把數量自增,再跳出 for 循環建立新的線程去執行任務) * 該方法內部也是調用了原子類 AtomicInteger.compareAndSet()方法,保證原子遞增 */ if (compareAndIncrementWorkerCount(c)) break retry; //若是嘗試添加新的工做線程失敗則會繼續判斷當前線程池的狀態,狀態知足繼續嘗試爲當前線程分配工做線程 c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } //跳出 for 循環以後說明線程池的工做線程數 workerCount 已經調節過了,接下來要作到就是真正的分配線程,執行任務 boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { //Worker 對象是個內部類,其實就是用threatFactory 生成一個新的線程 //繼承 AQS 類,實現Runable 接口,重寫 run()方法,重寫的 run()方法也很重要,後面會講 w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { //加鎖保證同步 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //仍是先進行一通的線程池狀態檢查 int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); //這個 workers 是個 HashSet,線程池也是經過維護這個 workers 控制任務的執行 workers.add(w); int s = workers.size(); if (s > largestPoolSize) //largestPoolSize記錄着線程池中出現過的最大線程數量 largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { //終於,調用線程的 start() 方法 t.start(); workerStarted = true; } } } finally { //若是建立線程失敗,就要回滾線程池的狀態了 if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
Worker 類
Worker 類是用來幹嗎的,存在的意義
回頭再看 Worker 類,線程池中的每個線程被封裝成一個Worker對象,ThreadPool維護的其實就是一組 Worker對象(HashSet)。
Worker類繼承了AQS,並實現了Runnable接口,注意其中的firstTask和thread屬 性:firstTask用它來保存傳入的任務;thread是在調用構造方法時經過ThreadFactory來創 建的線程,是用來處理任務的線程。
Worker繼承了AQS,使用AQS來實現獨佔鎖的功能。爲何不使用ReentrantLock來 實現呢?能夠看到tryAcquire方法,它是不容許重入的,而ReentrantLock是容許重入的:
- lock方法一旦獲取了獨佔鎖,表示當前線程正在執行任務中;
- 若是正在執行任務,則不該該中斷線程;
- 若是該線程如今不是獨佔鎖的狀態,也就是空閒的狀態,說明它沒有在處理任務, 這時能夠對該線程進行中斷;
- 線程池在執行shutdown方法或tryTerminate方法時會調用interruptIdleWorkers 方法來中斷空閒的線程,interruptIdleWorkers方法會使用tryLock方法來判斷線程 池中的線程是不是空閒狀態;
- 之因此設置爲不可重入,是由於咱們不但願任務在調用像setCorePoolSize這樣的 線程池控制方法時從新獲取鎖。若是使用ReentrantLock,它是可重入的,這樣若是 在任務中調用瞭如setCorePoolSize這類線程池控制的方法,會中斷正在運行的線程。 因此,Worker繼承自AQS,用於判斷線程是否空閒以及是否能夠被中斷。 此外,在構造方法中執行了setState(-1);,把state變量設置爲-1,爲何這麼作呢? 是由於AQS中默認的state是0,若是剛建立了一個Worker對象,尚未執行任務時,這時就不該該被中斷,看一下tryAquire方法:
Worker 類中以及涉及到的重要的方法
- tryAcquire(int unused) 方法
/** * 用於判斷線程是否空閒以及是否能夠被中斷 */ protected boolean tryAcquire(int unused) { //cas 修改狀態,不可重入 if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; }
tryAcquire方法是根據state是不是0來判斷的,因此,將state設置爲-1是 爲了禁止在執行任務前對線程進行中斷。
正由於如此,在runWorker方法中會先調用Worker對象的unlock方法將state設置爲 0。
- runWorker(Worker w)方法
/** * Worker 類實現 Runnable 接口,重寫的 run()方法 */ final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; //容許中斷 w.unlock(); // allow interrupts boolean completedAbruptly = true; try { //while 循環就是不斷的去執行任務,當本身的任務(firstTask)執行完以後依然會從阻塞隊列裏拿任務去執行,就這樣的操做保證了線程的重用 //task 爲空則從阻塞隊列中獲取任務 while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt /** * 若是線程池正在中止,那麼要保證當前線程是中斷狀態,若是不是的話,則要保證當前線程不是中斷狀態 * 這裏爲何要這麼作呢?考慮在執行該if語句期間可能也執行了shutdownNow方法,shutdownNow方法會 把狀態設置爲STOP, * 回顧一下STOP狀態:不能接受新任務,也不處理隊列中的任務,會中斷正在處理任務的線程。在線程池處於 RUNNING 或 SHUTDOWN 狀態時, * 調用 shutdownNow() 方法會使線程池進入到STOP狀態。 * STOP狀態要中斷線程池中的全部線程,而這裏使用Thread.interrupted()來判斷是否中斷是爲了確保在 RUNNING或者SHUTDOWN狀態時線程是非中斷狀態的, * 由於Thread.interrupted()方法會重置中斷的狀態。 */ 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 = false; } finally { processWorkerExit(w, completedAbruptly); } }
總結一下runWorker方法的執行過程:
- while循環不斷地經過getTask()方法獲取任務;
- getTask()方法從阻塞隊列中取任務;
- 若是線程池正在中止,那麼要保證當前線程是中斷狀態,不然要保證當前線程不是 中斷狀態;
- 調用task.run()執行任務;
- 若是task爲null則跳出循環,執行processWorkerExit()方法;
- runWorker方法執行完畢,也表明着Worker中的run方法執行完畢,銷燬線程。
- getTask()方法
/** * 從阻塞隊列中獲取任務,返回值是 Runnable * 線程池狀態不知足執行條件時直接返回 null */ private Runnable getTask() { //timeOut變量的值表示上次從阻塞隊列中取任務時是否超時 boolean timedOut = false; // Did the last poll() time out? //這裏兩個 for 循環操做和 addWorker() 方法裏的兩個 for 循環操做思想同樣 for (;;) { int c = ctl.get(); int rs = runStateOf(c); //仍然檢查線程池狀態 // Check if queue empty only if necessary. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); //timed變量用於判斷是否須要進行超時控制 //allowCoreThreadTimeOut默認是false,也就是核心線程不容許進行超時 //wc > corePoolSize,表示當前線程池中的線程數量大於核心線程數量 //對於超過核心線程數量的這些線程,須要進行超時控制 // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; //和 addWorker() 裏流程同樣,也是先對線程池中 workerCount 進行控制,再進行後面的執行任務操做 //知足條件則 workerCount 數量減一 if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { //根據timed來判斷,若是爲true,則經過阻塞隊列的poll方法進行超時控制,若是在keepAliveTime時間內沒有獲取到任務,則返回null //不然經過take方法,若是這時隊列爲空,則take方法會阻塞直到隊列不爲空 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; // 若是 r == null,說明已經超時,timedOut設置爲true timedOut = true; } catch (InterruptedException retry) { // 若是獲取任務時當前線程發生了中斷,則設置timedOut爲false並返回 timedOut = false; } } } processWorkerExit() /** * getTask方法返回null時,在runWorker方法中會跳出while循環,而後會執行 processWorkerExit方法。 * 作線程池的善後工做 */ private void processWorkerExit(Worker w, boolean completedAbruptly) { //若是completedAbruptly值爲true,則說明線程執行時出現了異常,須要將workerCount減1 //若是線程執行時沒有出現異常,說明在getTask()方法中已經已經對workerCount減1了,這裏就不須要再減了 if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //統計完成的任務數 completedTaskCount += w.completedTasks; // 從workers中移除,也就表示着從線程池中移除了一個工做線程 // workers 是前面提到的 HashSet,線程池就是經過維護這個 worker()來保證線程池運做的 workers.remove(w); } finally { mainLock.unlock(); } // 根據線程池狀態進行判斷是否結束線程池 tryTerminate(); /** * 當線程池是RUNNING或SHUTDOWN狀態時,若是worker是異常結束,那麼會直接addWorker; * 若是allowCoreThreadTimeOut=true,而且等待隊列有任務,至少保留一個worker * 若是allowCoreThreadTimeOut=false,workerCount很多於corePoolSize */ int c = ctl.get(); if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && ! workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } addWorker(null, false); } }
至此,processWorkerExit執行完以後,工做線程被銷燬,以上就是整個工做線程的生 命週期,從execute方法開始,Worker使用ThreadFactory建立新的工做線程, runWorker經過getTask獲取任務,而後執行任務,若是getTask返回null,進入 processWorkerExit方法,整個線程結束
4、思考
- 線程池如何實現線程重用的?
就是在重寫的 run()方法裏,經過 while 循環,執行完 firstTask 以後依然從阻塞隊列裏獲取任務去執行。
- 線程超時怎麼處理?
當前面任務拋出異常,後面的線程還會執行嗎? 答案是會。也是 while 循環裏找答案,當前線程拋出異常只會對當前線程產生影響,對線程池裏其餘任務不會有影響。
- 何時會銷燬?
是runWorker方法執行完以後,也就是Worker中的run方法執行完,由JVM自動回收。
- 阻塞隊列選取?在JDK中提供了以下阻塞隊列:
- ArrayBlockingQueue:基於數組結構的有界阻塞隊列,按FIFO排序任務;
- LinkedBlockingQuene:基於鏈表結構的阻塞隊列,按FIFO排序任務,吞吐量一般要高於ArrayBlockingQuene;
- SynchronousQuene:一個不存儲元素的阻塞隊列,每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於 LinkedBlockingQuene;
- PriorityBlockingQuene:具備優先級的無界阻塞隊列;
- 丟棄策略選取?線程池提供了4種策略:
- AbortPolicy:直接拋出異常,默認策略;
- CallerRunsPolicy:用調用者所在的線程來執行任務;
- DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;
- DiscardPolicy:直接丟棄任務;
- 線程數如何設置?
- 通常設法是會根據咱們任務的類型去設置,簡單分爲: CPU 密集型 :CPU 核數 + 1 IO 密集型:2*CPU 核數 + 1
《Java併發編程實戰》中最原始的公式是這樣的: Nthreads=Ncpu∗Ucpu∗(1+WC)Nthreads=Ncpu∗Ucpu∗(1+CW);
- Ncpu表明CPU的個數,
- Ucpu表明CPU利用率的指望值(0<Ucpu<10<Ucpu<1),
- WCCW仍然是等待時間與計算時間的比例。
上面提供的公式至關於目標CPU利用率爲100%。 一般系統中不止一個線程池,因此實際配置線程數應該將目標CPU利用率計算進去。