Java線程池原理解讀


引言

引用自《阿里巴巴JAVA開發手冊》

【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。

說明:使用線程池的好處是減少在創建和銷毀線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。

之前在閱讀《阿里巴巴JAVA開發手冊》時發現,其中有一條對於線程資源的值用限制,要求使用線程池來創建和維護,那么什么是線程池呢,為什么是線程池?原理是什么?怎么使用它?有什么講究呢?帶着這一系列的問題,我們開始來探究一下,希望這篇文章對我們有所收獲。

本文源碼來自JDK 1.8 。

簡介

線程池,故名思意,就是一個存放線程的池子,學術一點的說法,就是一組存放線程資源的集合。為什么有線程池這一概念地產生呢?想想以前我們都是需要線程的時候,直接自己手動來創建一個,然后執行完任務我們就不管了,線程就是我們執行異步任務的一個工具或者說載體,我們並沒有太多關注於這個線程自身生命周期對於系統或環境的影響,而只把重心放在了多線程任務執行完成的結果輸出,然后目的達到了,但是真正忽略了線程資源的維護和監控等問題。隨着大型系統大量多線程資源的使用,對多線程疏於重視、維護和管理而對資源占用和拉低性能的影響逐漸擴大,才引起了人們的思考。多線程的創建和銷毀在多線程的生命周期中占有很大比重,這一部分其實很占用資源和性能,如果使用線程來執行簡單任務,而因為線程本身的維護成本已經超出任務執行的效益,這是得不償失的,於是就產生了線程池。通過使用線程池,將線程的生命周期管控起來,同時能夠方便地獲取到線程、復用線程,避免頻繁地創建和銷毀線程帶來額外性能開銷,這大概就是線程池引入的背景和初衷吧。

所以現在看來,合理的利用線程池能夠給系統帶來幾大好處:

1、減低資源消耗。通過重復利用已創建好的線程來降低線程創建和銷毀造成的消耗;

2、提高響應速度。當任務到達時,任務可以不需要等待線程創建就能立馬執行;

3、提高線程可管理性。線程池時稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

但是,如果想合理的掌控線程池的使用,那么多線程池的原理和特性,都是必須要了解清楚的。

解讀

API繼承結構圖

JDK為我們提供一個叫做Excutor的框架來使用線程池,它是線程池的基礎,我們可以看下線程池相關的Java Diagram:

我們從上面的圖一個一個來看,分析下各個接口和類的功能和所承擔的角色。

Executor接口:

它是線程池的基礎,提供了唯一的一個方法execute用來執行線程任務。

ExecutorService接口:

它繼承了Executor接口,提供了線程池生命周期管理的幾乎所有方法,包括諸如shutdown、awaitTermination、submit、invokeAll、invokeAny等。

AbstractExecutorService類:

一個提供了線程池生命周期默認實現的抽象類,並且自行新增了如newTaskFor、doInvokeAny等方法。

ThreadPoolExecutor類:

這是線程池的核心類,也是我們常用來創建和管理線程池的類,我們使用Executors調用newFixedThreadPool、newSingleThreadExecutor和newCachedThreadPool等方法創建的線程池,都是ThreadPoolExecutor類型的。

ScheduledExecutorService接口:

賦予了線程池具備延遲和定期執行任務的能力,它提供了一些方法接口,使得任務能夠按照給定的方式來延期或者周期性的執行任務。

ScheduledThreadPoolExecutor類:

繼承自ThreadPoolExecutor類,同時實現了ScheduledExecutorService接口,具備了線程池所有通用能力,同時增加了延時執行和周期性執行任務的能力。

除了上面說到的這些,JDK1.7中還新增了一個線程池ForkJoinPool,它與ThreadPoolExecutor一樣繼承於AbstractExecutorService。與其他類型的ExecutorService相比,它的不同之處在於采用了工作竊取算法(work-stealing,可以從源碼和注釋中得到更多詳細介紹):所有線程池中的線程會嘗試找到並執行已被提交到池中的或由其他線程創建的任務。這樣的算法使得很少有線程處於空閑狀態,非常的高效,這樣的方式常用於如大多數由任務產生大量子任務的情況,以及像從外部客戶端大量提交小任務到池中的情況。

以上是對於線程池API繼承體系的簡單梳理和介紹,接下來我們深入源碼去進行分析。

線程池的幾種內部狀態

線程池使用了一個Integer類型變量來記錄線程池任務數量和線程池狀態信息,很巧妙。

(展開以查看代碼)

 1 /**
 2  * The main pool control state, ctl, is an atomic integer packing
 3  * two conceptual fields
 4  *   workerCount, indicating the effective number of threads
 5  *   runState,    indicating whether running, shutting down etc
 6  *
 7  * In order to pack them into one int, we limit workerCount to
 8  * (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2
 9  * billion) otherwise representable. If this is ever an issue in
10  * the future, the variable can be changed to be an AtomicLong,
11  * and the shift/mask constants below adjusted. But until the need
12  * arises, this code is a bit faster and simpler using an int.
13  *
14  * The workerCount is the number of workers that have been
15  * permitted to start and not permitted to stop.  The value may be
16  * transiently different from the actual number of live threads,
17  * for example when a ThreadFactory fails to create a thread when
18  * asked, and when exiting threads are still performing
19  * bookkeeping before terminating. The user-visible pool size is
20  * reported as the current size of the workers set.
21  *
22  * The runState provides the main lifecycle control, taking on values:
23  *
24  *   RUNNING:  Accept new tasks and process queued tasks
25  *   SHUTDOWN: Don't accept new tasks, but process queued tasks
26  *   STOP:     Don't accept new tasks, don't process queued tasks,
27  *             and interrupt in-progress tasks
28  *   TIDYING:  All tasks have terminated, workerCount is zero,
29  *             the thread transitioning to state TIDYING
30  *             will run the terminated() hook method
31  *   TERMINATED: terminated() has completed
32  *
33  * The numerical order among these values matters, to allow
34  * ordered comparisons. The runState monotonically increases over
35  * time, but need not hit each state. The transitions are:
36  *
37  * RUNNING -> SHUTDOWN
38  *    On invocation of shutdown(), perhaps implicitly in finalize()
39  * (RUNNING or SHUTDOWN) -> STOP
40  *    On invocation of shutdownNow()
41  * SHUTDOWN -> TIDYING
42  *    When both queue and pool are empty
43  * STOP -> TIDYING
44  *    When pool is empty
45  * TIDYING -> TERMINATED
46  *    When the terminated() hook method has completed
47  *
48  * Threads waiting in awaitTermination() will return when the
49  * state reaches TERMINATED.
50  *
51  * Detecting the transition from SHUTDOWN to TIDYING is less
52  * straightforward than you'd like because the queue may become
53  * empty after non-empty and vice versa during SHUTDOWN state, but
54  * we can only terminate if, after seeing that it is empty, we see
55  * that workerCount is 0 (which sometimes entails a recheck -- see
56  * below).
57  */
58 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
59 private static final int COUNT_BITS = Integer.SIZE - 3;
60 private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
61 
62 // runState is stored in the high-order bits
63 private static final int RUNNING    = -1 << COUNT_BITS;
64 private static final int SHUTDOWN   =  0 << COUNT_BITS;
65 private static final int STOP       =  1 << COUNT_BITS;
66 private static final int TIDYING    =  2 << COUNT_BITS;
67 private static final int TERMINATED =  3 << COUNT_BITS;
68 
69 // Packing and unpacking ctl
70 private static int runStateOf(int c)     { return c & ~CAPACITY; }
71 private static int workerCountOf(int c)  { return c & CAPACITY; }
72 private static int ctlOf(int rs, int wc) { return rs | wc; }
View Code

看這個變量ctl,被定義為了AtomicInteger,使用高3位來表示線程池狀態低29位來表示線程池中的任務數量

線程池狀態

RUNNING線程池能夠接受新任務,以及對新添加的任務進行處理。

SHUTDOWN線程池不可以接受新任務,但是可以對已添加的任務進行處理。

STOP線程池不接收新任務,不處理已添加的任務,並且會中斷正在處理的任務。

TIDYING當所有的任務已終止,ctl記錄的"任務數量"為0,線程池會變為TIDYING狀態。當線程池變為TIDYING狀態時,會執行鈎子函數terminated()。terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變為TIDYING時,進行相應的處理;可以通過重載terminated()函數來實現。

TERMINATED線程池徹底終止的狀態。

根據代碼設計,我們用圖標來展示一下

各線程池狀態的切換圖示

原理分析

原理分析,我們將結合源碼的和注釋的方式來分析。

核心參數

通過上面的描述我們知道,線程池的核心實現即ThreadPoolExecutor類就是本次我們重點關注和學習的對象。從對它的初始化過程我們看到,它完整的構造方法向我們暴露了這幾個核心的參數:

 1 public ThreadPoolExecutor(int corePoolSize,
 2                           int maximumPoolSize,
 3                           long keepAliveTime,
 4                           TimeUnit unit,
 5                           BlockingQueue<Runnable> workQueue,
 6                           ThreadFactory threadFactory,
 7                           RejectedExecutionHandler handler) {
 8     if (corePoolSize < 0 ||
 9         maximumPoolSize <= 0 ||
10         maximumPoolSize < corePoolSize ||
11         keepAliveTime < 0)
12         throw new IllegalArgumentException();
13     if (workQueue == null || threadFactory == null || handler == null)
14         throw new NullPointerException();
15     this.corePoolSize = corePoolSize;
16     this.maximumPoolSize = maximumPoolSize;
17     this.workQueue = workQueue;
18     this.keepAliveTime = unit.toNanos(keepAliveTime);
19     this.threadFactory = threadFactory;
20     this.handler = handler;
21 }

通過上面構造函數可以看出,ThreadPoolExecutor類具有7個參數,由於篇幅受限我沒有把構造函數的注釋文檔貼上來,我現在逐個翻譯並簡要說明一下:

corePoolSize核心線程數,當線程數小於該值時,線程池會優先創建新線程來執行任務,如果調用了線程池的prestartAllCoreThreads方法,線程池會提前創建並啟動所有基本線程,除非設置了allowCoreThreadTimeOut,否則核心線程將持續保留在線程池中即時沒有新的任務提交過來。

maximumPoolSize最大線程數,即線程池所能允許創建的最大線程數量。

keepAliveTime空閑線程存活時間,當線程數量大於核心線程數時,這是多余空閑線程在終止之前等待新任務的最長時間。

unitkeepAliveTime數值的時間單位。

workQueue任務隊列,用於緩存未執行的任務,隊列一直會持有任務直到有線程開始執行它。

threadFactory線程工廠,可以通過工廠創建更具識別性質的線程,如線程名字等。

handler拒絕策略,當線程和隊列都處於飽和時就使用拒絕策略來處理新任務。

線程池中線程的使用和創建規則

在Java線程池的實現邏輯中,線程池所能創建的線程數量受限於corePoolSize和maximumPoolSize兩個參數值,線程的創建時機則和corePoolSize和workQueue兩個參數相關,當線程數量和隊列長度都已達到飽和時,則介入拒絕策略來處理新的任務了,下面把大概的流程說明一下。

1、當來了新任務,如果線程池中空閑線程數量小於corePoolSize,則直接拿線程池中新的線程來處理任務;

2、如果線程池正在運行的線程數量大於等於corePoolSize,而此時workQueue隊列未滿,則將此任務緩存到隊列中;

3、如果線程池正在運行的線程數量大於等於corePoolSize,且workQueue隊列已滿,但現在的線程數量還小於maximumPoolSize,則創建新的線程來執行任務。

4、如果線程數量已經大於maximumPoolSize且workQueue隊列也已經滿了,則使用拒絕策略來處理該任務,默認的拒絕策略就是拋出異常(AbortPolicy)。

我們簡化下上面的文字,用簡單表格來展示:

我們再用流程圖來對線程池中提交任務的這一邏輯增加感性認識:

 下面,我們通過代碼,來重點性的解讀一下這一流程:

1、線程的創建和復用

線程池中線程的創建是通過線程工廠ThreadFactory來實現的,線程池的默認實現是使用Executors.defaultThreadFactory()來返回的工廠類,我們可以通過構造函數指定線程工廠,這里不做深入了解。

順便提一句,線程池的創建其實還有個關鍵方法prestartAllCoreThreads(),它的作用就是在線程池剛初始化的時候就激活核心線程數大小的線程放置到線程池中,等待任務着任務來執行,但是JDK默認的啟動策略中並沒有使用它,我按照這個方法查詢了一下,在Tomcat包中實現的ThreadPoolExecutor中在構造的時候,都調用了這個方法來初始化核心線程數量。

線程復用是線程池作用的關鍵所在,避免線程重復創建和銷毀,重復使用空閑的未銷毀的線程。所以這就要求一個線程在執行完一個任務之后不能直接退出,需要重新去隊列任務中獲取新的任務來執行,如果任務隊列中沒有任務,且keepAliveTime沒有被設置,那么這個工作線程將一直阻塞下去指導有新的任務可執行,這樣就達到了線程復用的目的。

(展開以查看代碼)

 1 private final class Worker
 2     extends AbstractQueuedSynchronizer
 3     implements Runnable
 4 {
 5     // 創建任務調用內部類Worker
 6     Worker(Runnable firstTask) {
 7         setState(-1); // inhibit interrupts until runWorker
 8         this.firstTask = firstTask;
 9         // 通過線程工廠來創建線程
10         this.thread = getThreadFactory().newThread(this);
11     }
12     
13     // 實現了Runnable接口
14     public void run() {
15         runWorker(this);
16     }
17 }
18 
19 // 執行Worker任務
20 final void runWorker(Worker w) {
21     Thread wt = Thread.currentThread();
22     Runnable task = w.firstTask;
23     w.firstTask = null;
24     w.unlock(); // allow interrupts
25     boolean completedAbruptly = true;
26     try {
27         // 循環從隊列中獲取任務
28         while (task != null || (task = getTask()) != null) {
29             w.lock();
30             // If pool is stopping, ensure thread is interrupted;
31             // if not, ensure thread is not interrupted.  This
32             // requires a recheck in second case to deal with
33             // shutdownNow race while clearing interrupt
34             if ((runStateAtLeast(ctl.get(), STOP) ||
35                  (Thread.interrupted() &&
36                   runStateAtLeast(ctl.get(), STOP))) &&
37                 !wt.isInterrupted())
38                 wt.interrupt();
39             try {
40                 beforeExecute(wt, task);
41                 Throwable thrown = null;
42                 try {
43                     // 執行線程任務
44                     task.run();
45                 } catch (RuntimeException x) {
46                     thrown = x; throw x;
47                 } catch (Error x) {
48                     thrown = x; throw x;
49                 } catch (Throwable x) {
50                     thrown = x; throw new Error(x);
51                 } finally {
52                     afterExecute(task, thrown);
53                 }
54             } finally {
55                 task = null;
56                 w.completedTasks++;
57                 w.unlock();
58             }
59         }
60         completedAbruptly = false;
61     } finally {
62         processWorkerExit(w, completedAbruptly);
63     }
64 }
View Code

2、提交線程任務

提交任務調用線程池的submit方法,該方法在AbstractExecutorService中。

1 public Future<?> submit(Runnable task) {
2     if (task == null) throw new NullPointerException();
3     // 創建任務
4     RunnableFuture<Void> ftask = newTaskFor(task, null);
5     // 執行任務
6     execute(ftask);
7     return ftask;
8 }

其中的execute接口在Executor接口中定義,具體的實現在ThreadPoolExecutor中得以體現。

(展開以查看代碼)

 1 public void execute(Runnable command) {
 2     if (command == null)
 3         throw new NullPointerException();
 4     /*
 5      * Proceed in 3 steps:
 6      *
 7      * 1. If fewer than corePoolSize threads are running, try to
 8      * start a new thread with the given command as its first
 9      * task.  The call to addWorker atomically checks runState and
10      * workerCount, and so prevents false alarms that would add
11      * threads when it shouldn't, by returning false.
12      *
13      * 2. If a task can be successfully queued, then we still need
14      * to double-check whether we should have added a thread
15      * (because existing ones died since last checking) or that
16      * the pool shut down since entry into this method. So we
17      * recheck state and if necessary roll back the enqueuing if
18      * stopped, or start a new thread if there are none.
19      *
20      * 3. If we cannot queue task, then we try to add a new
21      * thread.  If it fails, we know we are shut down or saturated
22      * and so reject the task.
23      */
24      // ctl記錄了線程數量和線程狀態
25     int c = ctl.get();
26     // 如果工作線程數小於核心線程數,創建新線程執行
27     // 即時其他線程是空閑的
28     if (workerCountOf(c) < corePoolSize) {
29         if (addWorker(command, true))
30             return;
31         c = ctl.get();
32     }
33     // 緩存任務到隊列中,這里進行了double check
34     // 如果線程池中運行的線程數量>=corePoolSize,
35     // 且線程池處於RUNNING狀態,且把提交的任務成功放入阻塞隊列中,
36     // 就再次檢查線程池的狀態。
37     // 1.如果線程池不是RUNNING狀態,且成功從阻塞隊列中刪除任務,
38     //   則該任務由當前 RejectedExecutionHandler 處理。
39     // 2.否則如果線程池中運行的線程數量為0,則通過
40     //   addWorker(null, false)嘗試新建一個線程,
41     //   新建線程對應的任務為null。
42     if (isRunning(c) && workQueue.offer(command)) {
43         int recheck = ctl.get();
44         // 任務無效則拒絕
45         if (! isRunning(recheck) && remove(command))
46             reject(command);
47         else if (workerCountOf(recheck) == 0)
48             addWorker(null, false);
49     }
50     // 添加新的工作線程,並在addWorker方法中的兩個for循環來判斷
51     // 如果以上兩個條件不成立,既沒能將任務成功放入阻塞隊列中,
52     // 且addWoker新建線程失敗,則該任務由當前
53     // RejectedExecutionHandler 處理。
54     else if (!addWorker(command, false))
55         // 采用拒絕策略
56         reject(command);
57 }
View Code

addWorker方法用於新增任務,第二個boolean參數表示線程數是否控制在核心線程數之內。

(展開以查看代碼)

 1 private boolean addWorker(Runnable firstTask, boolean core) {
 2     retry:
 3     for (;;) {
 4         int c = ctl.get();
 5         int rs = runStateOf(c);
 6 
 7         // Check if queue empty only if necessary.
 8         if (rs >= SHUTDOWN &&
 9             ! (rs == SHUTDOWN &&
10                firstTask == null &&
11                ! workQueue.isEmpty()))
12             return false;
13 
14         for (;;) {
15             int wc = workerCountOf(c);
16             if (wc >= CAPACITY ||
17                 wc >= (core ? corePoolSize : maximumPoolSize))
18                 return false;
19             if (compareAndIncrementWorkerCount(c))
20                 break retry;
21             c = ctl.get();  // Re-read ctl
22             if (runStateOf(c) != rs)
23                 continue retry;
24             // else CAS failed due to workerCount change; retry inner loop
25         }
26     }
27 
28     boolean workerStarted = false;
29     boolean workerAdded = false;
30     Worker w = null;
31     try {
32         // 創建工作線程
33         w = new Worker(firstTask);
34         final Thread t = w.thread;
35         if (t != null) {
36             final ReentrantLock mainLock = this.mainLock;
37             mainLock.lock();
38             try {
39                 // Recheck while holding lock.
40                 // Back out on ThreadFactory failure or if
41                 // shut down before lock acquired.
42                 int rs = runStateOf(ctl.get());
43 
44                 if (rs < SHUTDOWN ||
45                     (rs == SHUTDOWN && firstTask == null)) {
46                     if (t.isAlive()) // precheck that t is startable
47                         throw new IllegalThreadStateException();
48                     // 將線程放置於HashSet中,持有mainLock才可訪問
49                     // workers中記錄了池中真正任務線程數量
50                     workers.add(w);
51                     int s = workers.size();
52                     if (s > largestPoolSize)
53                         largestPoolSize = s;
54                     workerAdded = true;
55                 }
56             } finally {
57                 mainLock.unlock();
58             }
59             if (workerAdded) {
60                 t.start();
61                 workerStarted = true;
62             }
63         }
64     } finally {
65         if (! workerStarted)
66             addWorkerFailed(w);
67     }
68     return workerStarted;
69 }
View Code

3、關閉線程池

ThreadPoolExecutor提供了shutdown()和shutdownNow()兩個方法來關閉線程。

shutdown

將線程狀態設置為SHUTDOWN,同時中斷線程,按過去執行已提交任務的順序,發起一個有序的關閉命令,而且不再接收新的任務,最后嘗試將線程池狀態設置為TERMINATED。

shutdownNow

將線程狀態設置為STOP,中斷所有的任務且不再接收新任務,嘗試停止所有正在執行的任務、暫停等待處理的任務,並返回等待執行的任務列表。中斷線程使用Thread.interrupt方法,未響應中斷命令的任務是無法被中斷的。

JDK提供的常用的線程池

一般情況下我們都不直接用ThreadPoolExecutor類來創建線程池,而是通過Executors工具類去構建,通過Executors工具類我們可以構造5種不同的線程池。

newFixedThreadPool(int nThreads):

創建固定線程數的線程池,corePoolSize和maximumPoolSize是相等的,默認情況下,線程池中的空閑線程不會被回收的;

newCachedThreadPool:

創建線程數量不定的線程池,線程數量隨任務量變動,一旦來了新的任務,如果線程池中沒有空閑線程則立馬創建新的線程來執行任務,空閑線程存活時間60秒,過后就被回收了,可見這個線程池彈性很高;

newSingleThreadExecutor:

創建線程數量為1的線程池,等價於newFixedThreadPool(1)所構造的線程池;

newScheduledThreadPool(int corePoolSize):

創建核心線程數為corePoolSize,可執行定時任務的線程池;

newSingleThreadScheduledExecutor:

等價於newScheduledThreadPool(1)。

阻塞隊列

構造函數中的隊列允許我們自定義,隊列的意義在於緩存無法得到線程執行的任務,當線程數量大於corePoolSize而當前workQueue還沒有滿時,就需要將任務放置到隊列中。JDK提供了幾種類型的隊列容器,每種類型都具各自特點,可以根據實際場景和需要自行配置到線程池中。

ArrayBlockingQueue

有界隊列,基於數組結構,按照隊列FIFO原則對元素排序;

LinkedBlockingQueue

無界隊列,基於鏈表結構,按照隊列FIFO原則對元素排序,Executors.newFixedThreadPool()使用了這個隊列;

SynchronousQueue

同步隊列,該隊列不存儲元素,每個插入操作必須等待另一個線程調用移除操作,否則插入操作會一直被阻塞,Executors.newCachedThreadPool()使用了這個隊列;

PriorityBlockingQueue

優先級隊列,具有優先級的無限阻塞隊列。

拒絕策略

拒絕策略(RejectedExecutionHandler)也稱飽和策略,當線程數量和隊列都達到飽和時,就采用飽和策略來處理新提交過來的任務,默認情況下采用的策略是拋出異常(AbortPolicy),表示無法處理直接拋出異常,其實JDK提供了四種策略,也很好記,拒絕策略無非就是拋異常、執行或者丟棄任務,其中丟棄任務就分為丟棄自己或者丟棄隊列中最老的任務,下面簡要說明一下:

AbortPolicy丟棄新任務,並拋出 RejectedExecutionException

DiscardPolicy不做任何操作,直接丟棄新任務

DiscardOldestPolicy丟棄隊列隊首(最老)的元素,並執行新任務

CallerRunsPolicy由當前調用線程來執行新任務

使用技巧

使用了線程池技術未必能夠給工作帶來利好,在沒能正確理解線程池特性以及了解自身業務場景下而配置的線程池,可能會成為系統性能或者業務的瓶頸甚至是漏洞,所以在我們使用線程池時,除了對線程池本身特性了如指掌,還需要對自身業務屬性進行一番分析,以便配置出合理的高效的線程池以供項目使用,下面我們從這幾個方面來分析:

  • 任務的性質:CPU密集型任務,IO密集型任務和混合型任務。
  • 任務的優先級:高,中和低。
  • 任務的執行時間:長,中和短。
  • 任務的依賴性:是否依賴其他系統資源,如數據庫連接。

性質不同的任務可以用不同規模的線程池分開處理。CPU密集型任務配置盡可能少的線程數量,如配置Ncpu+1個線程的線程池,以減少線程切換帶來的性能開銷。IO密集型任務則由於需要等待IO操作,線程並不是一直在執行任務,則配置盡可能多的線程,如2*Ncpu混合型的任務,如果可以拆分,則將其拆分成一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那么分解后執行的吞吐率要高於串行執行的吞吐率,如果這兩個任務執行時間相差太大,則沒必要進行分解。我們可以通過Runtime.getRuntime().availableProcessors()方法獲得當前設備的CPU個數。

優先級不同的任務可以使用優先級隊列PriorityBlockingQueue來處理。它可以讓優先級高的任務先得到執行,需要注意的是如果一直有優先級高的任務提交到隊列里,那么優先級低的任務可能永遠不能執行。

當然,以上這些配置方式都是經驗值,實際當中還需要分析自己的項目場景經過多次測試方可得出最適合自己項目的線程池配置。

自問自答

面試的時候,可能會遇到面試官問下面的這樣幾個問題,看看你現在能不能回答,算是一種學習的自我檢查吧。

1、線程池原理,參數如何設置?

2、線程池有哪些參數,阻塞隊列用的是什么隊列,為什么?

3、線程池原理,為什么要創建線程池,創建線程池的方式?

4、創建線程池有哪幾個核心參數,如何合理配置線程池大小?

參考資料

1、https://juejin.im/post/5aedb6b651882522835e5e45

2、https://cloud.tencent.com/developer/article/1109643

3、http://ifeve.com/java-threadpool/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM