寫在前面: 我是「揚帆向海」,這個暱稱來源於我的名字以及女朋友的名字。我熱愛技術、熱愛開源、熱愛編程。
技術是開源的、知識是共享的。
這博客是對自己學習的一點點總結及記錄,如果您對 Java、算法 感興趣,可以關注我的動態,我們一起學習。
用知識改變命運,讓我們的家人過上更好的生活
。
文章目錄
一、什麼是線程池
線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然後在創建線程後自動啓動這些任務。線程池線程都是後臺線程。每個線程都使用默認的堆棧大小,以默認的優先級運行,並處於多線程單元中。如果某個線程在託管代碼中空閒(如正在等待某個事件),則線程池將插入另一個輔助線程來使所有處理器保持繁忙。如果所有線程池線程都始終保持繁忙,但隊列中包含掛起的工作,則線程池將在一段時間後創建另一個輔助線程但線程的數目永遠不會超過最大值。超過最大值的線程可以排隊,但他們要等到其他線程完成後才啓動。
二、爲什麼要使用線程池
當系統創建一個新的線程,因爲它涉及到與操作系統的交互,所以它的成本是比較高的。經常創建和銷燬、使用量特別大的資源,比如併發情況下的線程,對性能影響很大。當程序中需要使用存活週期很短的線程時,應該考慮使用線程池。
三、JDK源碼中的線程池
// 創建一個線程池,該線程池重用在一個共享的無界隊列上運行的固定數量的線程。
Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
// 在任何時候,在大多數情況下線程都是活動的處理任務。
At any point, at most {@code nThreads} threads will be active processing tasks.
// 如果在所有線程都處於活動狀態時提交額外的任務,它們將在隊列中等待,直到有一個線程可用爲止。
If additional tasks are submitted when all threads are active, they will wait in the queue until a thread is available.
// 如果任何線程在關閉之前的執行過程中由於失敗而終止,那麼如果需要執行後續任務,則需要一個新線程來替代它
If any thread terminates due to a failure during execution prior to shutdown, a new one will take its place if needed to execute subsequent tasks.
// 池中的線程將一直存在,直到顯式結束
The threads in the pool will exist until it is explicitly.
四、使用線程池執行線程任務的步驟
- 調用
Executors
類的靜態方法創建 ExecutorService 對象 - 創建一個類,實現 Runnable 接口
- 調用
ExecutorService
對象的 execute() 方法來執行Runnable接口實例 - 最後當不再使用時,調用
ExecutorService
對象的shutdown
() 方法關閉線程池
Executors 是一個工廠類,主要用來創建 ExecutorService
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
用 newFixedThreadPool() 這個靜態方法用來創建一個可重用固定線程數的線程池。
示例代碼:
// 創建一個實現Runnable接口的類 Thread1
class Thread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 50; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "的i值爲: " + i);
}
}
}
}
// 創建一個實現Runnable接口的類 Thread2
class Thread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 50; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + "的i值爲: " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
// 創建一個固定線程數的的線程池
ExecutorService pool = Executors.newFixedThreadPool(10);
// 向線程中提交兩個任務,執行指定的線程的操作。需要提供實現Runnable接口實現類的對象
pool.execute(new Thread1());
pool.execute(new Thread2());
// 關閉線程池
pool.shutdown();
}
}
pool-1-thread-2的i值爲: 1
pool-1-thread-2的i值爲: 3
pool-1-thread-2的i值爲: 5
pool-1-thread-2的i值爲: 7
pool-1-thread-2的i值爲: 9
pool-1-thread-2的i值爲: 11
pool-1-thread-2的i值爲: 13
pool-1-thread-2的i值爲: 15
pool-1-thread-2的i值爲: 17
pool-1-thread-2的i值爲: 19
pool-1-thread-2的i值爲: 21
pool-1-thread-2的i值爲: 23
pool-1-thread-2的i值爲: 25
pool-1-thread-2的i值爲: 27
pool-1-thread-2的i值爲: 29
pool-1-thread-2的i值爲: 31
pool-1-thread-2的i值爲: 33
pool-1-thread-2的i值爲: 35
pool-1-thread-2的i值爲: 37
pool-1-thread-2的i值爲: 39
pool-1-thread-2的i值爲: 41
pool-1-thread-2的i值爲: 43
pool-1-thread-2的i值爲: 45
pool-1-thread-2的i值爲: 47
pool-1-thread-2的i值爲: 49
pool-1-thread-1的i值爲: 0
pool-1-thread-1的i值爲: 2
pool-1-thread-1的i值爲: 4
pool-1-thread-1的i值爲: 6
pool-1-thread-1的i值爲: 8
pool-1-thread-1的i值爲: 10
pool-1-thread-1的i值爲: 12
pool-1-thread-1的i值爲: 14
pool-1-thread-1的i值爲: 16
pool-1-thread-1的i值爲: 18
pool-1-thread-1的i值爲: 20
pool-1-thread-1的i值爲: 22
pool-1-thread-1的i值爲: 24
pool-1-thread-1的i值爲: 26
pool-1-thread-1的i值爲: 28
pool-1-thread-1的i值爲: 30
pool-1-thread-1的i值爲: 32
pool-1-thread-1的i值爲: 34
pool-1-thread-1的i值爲: 36
pool-1-thread-1的i值爲: 38
pool-1-thread-1的i值爲: 40
pool-1-thread-1的i值爲: 42
pool-1-thread-1的i值爲: 44
pool-1-thread-1的i值爲: 46
pool-1-thread-1的i值爲: 48
pool-1-thread-1的i值爲: 50
五、剖析ThreadPoolExecutor 底層源碼
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
1. 創建一個線程池時的參數
corePoolSize
表示核心池的大小。
當提交一個任務到線程池的時候,線程池將會創建一個線程來執行任務。這個值的大小設置非常關鍵,設置過小將頻繁地創建或銷燬線程,設置過大則會造成資源浪費。
線程池新建線程的時候,如果當前線程總數小於corePoolSize,則新建的是核心線程;如果超過corePoolSize,則新建的是非核心線程。
maximumPoolSize
表示線程池的最大數量。
如果隊列 (workQueue) 滿了,並且已創建的線程數小於maximumPoolSize,則線程池會進行創建新的線程執行任務。 如果
等待執行的線程數 大於 maximumPoolSize,緩存在隊列中; 如果 maximumPoolSize 等於 corePoolSize,即是固定大小線程池。
keepAliveTime
表示線程活動的保持時間
當需要執行的任務很多,線程池的線程數大於核心池的大小時,keepAliveTime才起作用;
當一個非核心線程,如果不幹活(閒置狀態)的時長超過這個參數所設定的時長,就會被銷燬掉
TimeUnit
表示線程活動保持時間的單位
它的單位有:TimeUnit.DAYS,TimeUnit.HOURS,TimeUnit.MINUTES,TimeUnit.MILLISECONDS,TimeUnit.MICRODECONDS
workQueue
表示線程池中存放被提交但尚未被執行的任務的隊列
維護着等待執行的 Runnable對象。當所有的核心線程都在幹活時,新添加的任務會被添加到這個隊列中等待處理,如果隊列滿了,則新建非核心線程執行任務。
threadFactory
用於設置創建線程的工廠
它用來給每個創建出來的線程設置一個名字,就可以知道線程任務是由哪個線程工廠產生的。
handler
表示拒絕處理策略
線程數量大於最大線程數,當超過workQueue的任務緩存區上限的時候,就可以調用該策略,這是一種簡單的限流保護。
從JDK 源碼裏面可以看到:
其中裏面其設置的 corePoolSize
(核心池的大小) 和 maximumPoolSize
(最大線程數) 都是 nThreads,其設定的阻塞隊列是無界的,則飽和策略將失效,所有請求將一直排隊等待被執行,可能會產生內存溢出的風險。因此阿里巴約編碼規範不推薦用Executors來創建ThreadPoolExecutor。
使用Executors創建線程池時要明確創建的阻塞隊列是否有界。因此最好自己創建ThreadPoolExecutor。
ThreadPoolExecutor pool1 = (ThreadPoolExecutor) pool;
// 設置核心池的大小
pool1.setCorePoolSize(15);
// setkeepAliveTime()方法 設置線程沒有任務時最多保持多長時間後會停止
pool1.setKeepAliveTime(60000, TimeUnit.HOURS);
Executors的工廠方法主要就返回了ThreadPoolExecutor對象
繼承結構:
ThreadPoolExecutor 繼承了 AbstractExecutorService
public class ThreadPoolExecutor extends AbstractExecutorService {
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 表示線程池數量的位數,很明顯是29,Integer.SIZE=32
private static final int COUNT_BITS = Integer.SIZE - 3;
// 表示線程池最大數量,2^29 - 1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
/*
* Bit field accessors that don't require unpacking ctl.
* These depend on the bit layout and on workerCount being never negative.
*/
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
/**
* Attempts to CAS-increment the workerCount field of ctl.
*/
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
/**
* Attempts to CAS-decrement the workerCount field of ctl.
*/
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
/**
* Decrements the workerCount field of ctl. This is called only on
* abrupt termination of a thread (see processWorkerExit). Other
* decrements are performed within getTask.
*/
private void decrementWorkerCount() {
do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}
/**
* The queue used for holding tasks and handing off to worker
* threads. We do not require that workQueue.poll() returning
* null necessarily means that workQueue.isEmpty(), so rely
* solely on isEmpty to see if the queue is empty (which we must
* do for example when deciding whether to transition from
* SHUTDOWN to TIDYING). This accommodates special-purpose
* queues such as DelayQueues for which poll() is allowed to
* return null even if it may later return non-null when delays
* expire.
*/
// 用於存放線程任務的阻塞隊列
private final BlockingQueue<Runnable> workQueue;
/**
* Lock held on access to workers set and related bookkeeping.
* While we could use a concurrent set of some sort, it turns out
* to be generally preferable to use a lock. Among the reasons is
* that this serializes interruptIdleWorkers, which avoids
* unnecessary interrupt storms, especially during shutdown.
* Otherwise exiting threads would concurrently interrupt those
* that have not yet interrupted. It also simplifies some of the
* associated statistics bookkeeping of largestPoolSize etc. We
* also hold mainLock on shutdown and shutdownNow, for the sake of
* ensuring workers set is stable while separately checking
* permission to interrupt and actually interrupting.
*/
// 重入鎖
private final ReentrantLock mainLock = new ReentrantLock();
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
// 線程池當中的線程集合,只有當擁有mainLock鎖的時候,纔可以進行訪問
private final HashSet<Worker> workers = new HashSet<Worker>();
/**
* Wait condition to support awaitTermination
*/
// 等待條件支持終止
private final Condition termination = mainLock.newCondition();
/**
* Tracks largest attained pool size. Accessed only under
* mainLock.
*/
private int largestPoolSize;
/**
* Counter for completed tasks. Updated only on termination of
* worker threads. Accessed only under mainLock.
*/
private long completedTaskCount;
/*
* All user control parameters are declared as volatiles so that
* ongoing actions are based on freshest values, but without need
* for locking, since no internal invariants depend on them
* changing synchronously with respect to other actions.
*/
/**
* Factory for new threads. All threads are created using this
* factory (via method addWorker). All callers must be prepared
* for addWorker to fail, which may reflect a system or user's
* policy limiting the number of threads. Even though it is not
* treated as an error, failure to create threads may result in
* new tasks being rejected or existing ones remaining stuck in
* the queue.
*
* We go further and preserve pool invariants even in the face of
* errors such as OutOfMemoryError, that might be thrown while
* trying to create threads. Such errors are rather common due to
* the need to allocate a native stack in Thread.start, and users
* will want to perform clean pool shutdown to clean up. There
* will likely be enough memory available for the cleanup code to
* complete without encountering yet another OutOfMemoryError.
*/
// 創建新線程的線程工廠
private volatile ThreadFactory threadFactory;
/**
* Handler called when saturated or shutdown in execute.
*/
// 飽和策略
private volatile RejectedExecutionHandler handler;
總結:
ctl是主要的控制狀態
workerCount:表示有效的線程數目
runState:表示線程池裏線程的運行狀態
2. 重要方法
① execute() 方法
源碼剖析
// 調用execute方法將線程提交到線程池中
public void execute(Runnable command) {
// 如果執行的任務爲空,則會拋出空指針異常
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
// 獲取線程池的控制狀態
int c = ctl.get();
// 如果workerCount值小於corePoolSize
if (workerCountOf(c) < corePoolSize) {
// 添加任務到worker集合當中,成功的話返回
if (addWorker(command, true))
return;
// 如果失敗,再次獲取線程池的控制狀態
c = ctl.get();
}
// 如果corePoolSize已經滿了,則需要加入到阻塞隊列
// 判斷線程池的狀態以及是否可以往阻塞隊列中繼續添加runnable
if (isRunning(c) && workQueue.offer(command)) {
// 獲取線程池的狀態
int recheck = ctl.get();
// 再次檢查狀態,線程池不處於RUNNING狀態,將任務從workQueue隊列中移除
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果此時隊列已滿,則會採取相應的拒絕策略
else if (!addWorker(command, false))
reject(command);
}
總結:
- 往corePoolSize中加入任務進行執行
- 當corePoolSize滿時往阻塞隊列中加入任務
- 阻塞隊列滿時並且maximumPoolSize已滿,則採取相應的拒絕策略
② addWorker()方法
源碼剖析
private boolean addWorker(Runnable firstTask, boolean core) {
// 外部循環標誌
retry:
for (;;) {
// 獲取線程池的狀態
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
// 獲取workerCount
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 跳出循環
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
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 實例化worker對象
w = new Worker(firstTask);
// 獲取worker的線程
final Thread t = w.thread;
// 如果獲取的線程不爲空
if (t != null) {
// 初始線程池的鎖
final ReentrantLock mainLock = this.mainLock;
// 得到鎖
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
// 獲取鎖後再次檢查,獲取線程池runState
int rs = runStateOf(ctl.get());
// 判斷
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 檢查線程是否啓動
if (t.isAlive()) // precheck that t is startable
// 如果未啓動存活,則拋出異常
throw new IllegalThreadStateException();
// 往corePoolSize中加入任務
workers.add(w);
// 獲取workers集合的大小
int s = workers.size();
// 如果大小超過largestPoolSize
if (s > largestPoolSize)
// 重新設置線程池擁有最大線程數的大小
largestPoolSize = s;
// 改變狀態
workerAdded = true;
}
} finally {
// 釋放鎖
mainLock.unlock();
}
if (workerAdded) {
// 運行
t.start();
// 改變狀態
workerStarted = true;
}
}
} finally {
// 如果worker沒有啓動成功
if (! workerStarted)
addWorkerFailed(w);
}
// 返回worker是否成功啓動的標記
return workerStarted;
}
六、使用線程池的好處
- 降低資源消耗 。重複利用線程池中已經創建好的線程,不需要每次都創建,降低創建和銷燬造成的消耗。
- 提高響應的速度。當任務分配下來時,任務無需等到創建線程就能被執行,減少了創建線程的時間。
- 方便進行線程管理。線程無限制的被創建,會佔用系統資源並且還會降低系統的穩定性。使用線程池可以進行統一管理,設置核心池的大小,設置線程沒有任務時最多保持多長時間。
由於水平有限,本博客難免有不足,懇請各位大佬不吝賜教!