https://www.jianshu.com/p/117571856b28
https://juejin.im/entry/58fada5d570c350058d3aaad
概述
這篇筆記也是整理於寒假實習期間,由於美顏相機demo中的縮略圖渲染功能涉及到多線程,使用了線程池減少資源開銷,所以整理一下線程池筆記,以便以後查詢(待完善)。
使用線程池的主要目的在於:
1. 降低資源消耗
2. 提高響應速度
3. 提高線程的可管理性
幾個參數
線程池的構造器中主要有以下幾個參數
1. corePoolSize:核心線程的數量。核心線程是在執行完任務之後也不會被銷燬的線程。
2. maximumPoolSize:線程池中的最大線程數。表示線程池中最多能同時存在多少個線程。
3. keepAliveTime:表示線程沒有任務執行時最多能存活多久。默認情況下只在線程數大於corePoolSize時起作用。但是通過調用allowCoreThreadTimeOut(boolean)方法可以在線程池中數量不超過corePoolSize時也會起作用,直到線程池中的線程數爲0。
4. unit:keepAliveTime的時間單位,具體取值看源碼。
5. workQueue:一個阻塞隊列,用來存儲等待執行的任務。一般情況下有以下幾種隊列:
* ArrayBlockingQueue:基於數組的有界阻塞隊列,按照FIFO原則對元素排序。
* LinkedBlockingQueue:基於鏈表的無界阻塞隊列,按照FIFO排序。Executors.newFixedThreadPool()使用了這個隊列。
* SynchronizedQueue:一個不存儲元素的阻塞隊列,每個插入操作都必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態。Executors.newCachedThreadPool使用了這個隊列。
* PriorityBlockingQueue:一個具有優先級的無限阻塞隊列。
6. threadFactory:線程工廠,用於創建線程。
7. handler:飽和策略,即當隊列和線程池都滿了,應該採取什麼策略來處理新提交的任務。通常有以下四種策略:
* ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException。
* ThreadPoolExecutor.DiscardPolicy:丟棄任務,但是不拋異常。
* ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務。
* ThreadPoolExecutor.CallerRunsPolicy:由調用線程執行任務。
線程池的原理
線程的狀態
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
由Thread內部定義的枚舉類型State可以看出線程共有六種狀態:
1. NEW:新建線程,但是還沒調用start()方法。處於NEW狀態的線程有自己的內存空間。
2. RUNNABLE:可運行狀態,線程對象創建且調用了start()方法後進入該狀態,該狀態的線程位於可運行線程池中,等待被線程調度器選中,獲取CPU的使用權。
3. BLOCKED:等待獲取鎖時進入的狀態,線程被掛起,通常是因爲它在等待一個鎖。當某個synchronized正好有線程在使用時,一個線程嘗試進入這個臨界區,就會被阻塞。當它搶到鎖之後,纔會從BLOCKED狀態轉換爲RUNNABLE狀態。
4. WAITING:當調用wait()、join()、park()方法時,進入WAITING狀態。前提是這個線程已經擁有鎖。
WAITING狀態與BLOCKED狀態的區別在於:①、BLOCKED是虛擬機認爲程序還不能進入某個臨界區。 ②、WAITING狀態的先決條件是當前線程已經在臨界區中,但是它自己通過判定業務上的參數,發現還有一些其他配合的資源沒有準備充分而選擇等待再做其他事情。
可以通過notify/notifyAll動作,從等待池中喚醒線程重新恢復到RUNNABLE狀態。
5. TIMED_WAITING:通過wait(t)、sleep(t)、join(t)等方法進入此狀態。當時間達到時觸發線程回到工作狀態RUNNABLE。interrupt只對WAITING或者TIMED_WAITING狀態的線程起作用。
6. TERMINATED:線程結束後就是這種狀態。也就是run方法執行完畢。
線程池的狀態
// 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;
由ThreadPoolExecutor中定義的常量可以知道線程池由五種狀態:
1. RUNNING:能接受新提交的任務,並且也能處理阻塞隊列的任務。
2. SHUTDOWN:關閉狀態,不再接受新的任務,但可以繼續處理阻塞隊列中一保存的任務。調用shutdown()方法會進入此狀態。
3. STOP:不能接受新任務,也不能處理隊列中的任務。調用shutdownNow()方法會進入此狀態。
4. TIDYING:如果所有的任務都已終止,workerCount(有效線程數)爲0,線程池會進入這個狀態。只有會調用terminated()方法進入TERMINATED狀態。
5. TERMINATED:終止狀態。
狀態轉換圖如下:
執行過程
我們通過submit(Runnable)方法來提交任務時,執行代碼如下:
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
它會把Runnable對象封裝成一個RunnableFuture對象。然後調用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.
*/
//通過ctl.get()方法獲取int值,記錄了線程池當前的runState與workerCount。
int c = ctl.get();
//然後通過workerCountOf方法取出低29位的值,表示當前活動的線程數。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
//如果添加失敗,重新獲取ctl值。
c = ctl.get();
}
//如果當前線程池是Running狀態,且成功將任務添加進任務隊列中
if (isRunning(c) && workQueue.offer(command)) {
//再次獲取ctl值,如果不是運行狀態,把剛添加進去的任務移除。
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
//調用reject方法,根據設定的拒絕策略執行不同的操作。
reject(command);
//如果當前有效線程數是0,則執行addWorker操作。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//執行到這裏,有兩種情況:①、線程池已經不是RUNNING狀態。②、線程池是RUNNING狀態,但workerCount >= corePoolSize且workQueue已滿。則再次執行addWorker方法。
else if (!addWorker(command, false))
//如果失敗則拒絕執行任務。
reject(command);
}
相關解析看代碼中的註釋。
關於addWorker方法,有兩個參數 Runnable firstTask 與 boolean core。第一個參數爲null,表示在線程池中創建一個線程但不會啓動。第二個參數爲true,表示將線程池的線程數量上限設置爲corePoolSize,爲false表示將上限設置爲maximumPoolSize。
線程池的調度方式
怎樣開始執行
線程池通過addWorker方法成功創建一個新線程之後,會調用新線程的start方法。
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 {
w = new Worker(firstTask);
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.
int rs = runStateOf(ctl.get());
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;
}
t.start()句代碼中,t實際是Worker中的Thread類型常量:
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
thread是在Worker的構造方法中被初始化的:
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
在Worker的構造方法中,可以看到通過ThreadFactory創建了一個新的線程,同時傳入了this,即是當前的worker對象。Woker類實現了Runnable接口,因此可以作爲參數傳入。這樣,當在addWorker()方法中調用t.start()方法時,實際調用的就是Worker對象中的run()方法。那麼接着看Worker中的run()方法:
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
裏面只有一句,調用runWorker(this)。進入runWorker(this)瞧瞧:
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 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 ((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);
}
}
基本原理就是通過循環,不斷地從任務隊列中獲取任務,並執行。在上面的源碼中可以看到,beforeExecute()/afterExecute()方法也是在這裏面被調用的。
怎樣結束執行
AQS
戲(細)說Executor框架線程池任務執行全過程(下)
http://www.infoq.com/cn/articles/executor-framework-thread-pool-task-execution-part-02
線程池中的異常捕獲
合理配置線程池(待整理):
http://gityuan.com/2016/01/16/thread-pool/
4.1 合理地配置線程池
需要針對具體情況而具體處理,不同的任務類別應採用不同規模的線程池,任務類別可劃分爲CPU密集型任務、IO密集型任務和混合型任務。
對於CPU密集型任務:線程池中線程個數應儘量少,不應大於CPU核心數;
對於IO密集型任務:由於IO操作速度遠低於CPU速度,那麼在運行這類任務時,CPU絕大多數時間處於空閒狀態,那麼線程池可以配置儘量多些的線程,以提高CPU利用率;
對於混合型任務:可以拆分爲CPU密集型任務和IO密集型任務,當這兩類任務執行時間相差無幾時,通過拆分再執行的吞吐率高於串行執行的吞吐率,但若這兩類任務執行時間有數據級的差距,那麼沒有拆分的意義。