一、線程池實現原理
Java支持多線程,多線程可以提高任務的執行效率。但是Java里面的線程跟操作系統的線程是一一對應的,所以創建過多的線程會對系統資源產生很大的消耗,同時過多的線程競爭CPU,也會產生頻繁的上下文切換,結果可能適得其反,降低系統的運行效率。
線程池的作用就是對線程的重復利用,把線程數量控制在合理的范圍內,避免上述情況的產生。
線程池有幾個部分組成:
- 任務隊列:要執行的任務集合
- 活躍的線程:用來執行任務的線程
- 任務調度:控制任務執行
所以線程池的基本原理可以用下圖來表示:
二、Java線程池類圖
- Executor : 定義任務提交執行execute,執行的線程可能是新的線程,線程池的線程,或者調用者的線程,取決于線程池的實現。
- ExecutorService : 定義了有返回值的任務提交submit,線程池的中斷。
- AbstractExecutorService : 實現submit方法
- ScheduledExecutorService : 定義定時執行的方法
- ThreadPoolExecutor : 線程池的主要實現類
- ScheduledThreadPoolExecutor : 實現定時執行線程池的邏輯
三、ThreadPoolExecutor源碼分析
ThreadPoolExecutor是線程池的核心類,包含線程池的主要實現邏輯。
3.1、主要成員變量
- AtomicInteger ctl :表示線程池當前的狀態(高3位)&有效線程數量(低29位)
- BlockingQueue<Runnable> workQueue : 任務隊列
- HashSet<Worker> workers : 活躍線程集合
- int corePoolSize : 核心線程數
- int maximumPoolSize : 最大線程數
- RejectedExecutionHandler defaultHandler : 任務拒絕策略
3.2、execute
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
提交任務時,如果當前線程數量小于核心線程數,不管是否有線程空閑,都會新建一個線程來執行任務,當線程數量大于核心線程,小于最大線程時,只有當workQueue滿的時候,才會新建線程來執行,當線程數量等于最大線程數時,并且workQueue滿的時候,會執行任務拒絕策略。
addWorder
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 (;;) {
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 {
final ReentrantLock mainLock = this.mainLock;
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
int rs = runStateOf(c);
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker封裝了執行任務的線程,它可以用一個任務初始化。新增一個worker首先判斷當前線程池的狀態還是否允許新增一個線程。如果可以,則會CAS更新workCount。然后new一個worker,調用worker線程的start方法。
worker線程是實現了Runnable的方法,worker線程會調用它的run方法,進而調用runWorker
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 (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 = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
這個方法就會判斷一些狀態,然后調用Task的run方法,執行任務。
四、ScheduledThreadPoolExecutor源碼分析
ScheduledThreadPoolExecutor 繼承自ThreadPoolExecutor,本身自帶了線程池的能力,只需要實現定時執行功能即可。
定時執行的基本原理是存儲任務的隊列是一個優先級隊列,按執行時間的先后排序,任務線程去隊列獲取最近執行的任務,如果任務還沒有到執行時間,則等待直到可以執行。
ScheduledThreadPoolExecutor 會把要定時執行的任務封裝成一個ScheduledFutureTask。
ScheduledFutureTask 有幾個成員變量:
- time 需要執行的時間
- period 執行周期
- sequenceNumber 序列號,如果時間相同,序列號小的先執行
利用這三個參數,結合優先級隊列,可以實現定時執行。
DelayedWorkQueue.take
public RunnableScheduledFuture take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture first = queue[0];
if (first == null)
available.await();
else {
long delay = first.getDelay(TimeUnit.NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
else if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
先去獲取位于隊首的任務,如果為NULL則wait,然后看時間是否到達,如果到達了,返回任務,如果沒有,則等待現在距離任務執行之間的時間差。
四、線程池使用注意
- 盡量使用 new ThreadPoolExecutor來構建線程池,這樣可以自己去控制和判斷線程池的大小。
- 如果是CPU密集型,線程數量可以設置為CPU核數+1
- 如果是IO密集型,線程數量可以設置為2*CPU核數+1