前言
多線程編程是一個難點,好多面試官都會問。雖然平時用到多線程編程的地方比較少,掌握基本的用法事半功倍。
合理使用多線程的好處
1.降低資源消耗
2.提高響應速度
3.提高線程的可管理性
頻繁的創建的Thread 類比較消耗系統資源,比較好的做法就是使用線程池來管理線程。
線程池的實現原理
當向線程池提交一個任務之後,線程池是如何處理這個任務的呢?
1.判斷核心線程池是否已滿,未滿則創建新線程,如果已滿則執行下個步驟
2.線程池判斷工作隊列是否已滿,未滿則將任務存儲在隊列裏,否則執行下個步驟
3.線程池中普通線程是否已滿,未滿則創建線程執行任務,否則交給飽和策略處理這個任務。
ThreadPoolExecutor 執行execute方法分四步:
1.如果當前運行的線程少於corePoolSize,則創建新線程來執行任務【執行需要獲取全局鎖】
2.如果執行的線程大於等於corePoolSize,則將當前線程放入Blockingqueue。
3.如果無法將任務加入BlockingQueue中,則創建新線程來執行任務【執行需要獲取全局鎖】
4.如果創建的新線程使當前運行的線程總數超過maxnumPoolSize,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()
源碼展示:
ThreadPoolExecutor.execute(Runnable command) 類
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
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();
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);
}
tips:這裏引入一個Worker對象,該對象是線程池創建時,將Thread 封裝成worker工作線程。
而,工作線程會循環獲取BlockingQueue中的線程來執行。
線程池的使用
1.創建
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
參數依次代表的意思:
1.定義核心線程數
2線程池最大線程數量
3.空閒線程存貨的時間
4.時間單位
5.線程池緩衝隊列,
6自定義線程池創建工廠
7.線程池飽和後的處理策略
tips:實現阻塞隊列的幾個類分別是
ArrayBlockingQueue:基於數組結構的有界阻塞隊列,FIFO對入隊元素進行排序
LinkedBlockingQueue:基於鏈表數據結構的阻塞隊列,吞吐率高於ArrayBlockingQueue隊列。
SynchronousQueue: 一個不存儲元素的阻塞隊列,插入必須等待着另一個線程調用移除操作,否則插入操作一直處於阻塞狀態。吞吐量高於LinkedBlockingQueue
priorityBlockingQueue:一個具有優先級的無線阻塞隊列
向線程池提交任務
接受實現了兩種Runnable,Callable結構方式類 【Callable有返回值】,這裏返回Future對象可以獲取線程執行狀況信息,線程是否執行成功。
tips:
new Future().get 會阻塞當前線程知道任務完成有返回值爲止。
關閉線程池
ThreadPoolExecutor 類提供兩種關閉方式
1.shutdown()
2.shutdownNow()
區別:
shutdown,shutdownNow,都是遍歷線程池中的工作線程,然後調用線程的Interrupt方法來中斷的。
shutdown將線程狀態置成SHUTDOWN 然後中斷所有沒有正在執行的任務。
shutdownNow 將線程狀態置成Stop,然後嘗試停止所有正在執行或者暫停任務的線程並返回等待執行任務的列表。
只要調用了這兩個方法中的一個isShutDown的方法就會返回True,當所有任務全部關閉後isTerminaed纔會True。
合理地配置線程池的原則
任務性質:cpu密集型,還是i/o密集型和混合任務
任務的優先級:高中低
任務的執行時間:長短中
任務的依賴性:是否依賴其他資源
cpu密集型,應該少分配線程,cpu+1個。i/o密集型多分配線程2*cpu數量。線程池優先使用有界線程池
線程池的監控
如果系統中大量使用線程池,有必要對線程池進行監控,方便在出現問題是,對使用狀況快速定位。
常用的幾個api
beforeExecute,afterExecute 和 terninated ,任務執行前,執行後和線程池關閉前執行一些代碼來進行監控
總結:
工作中線程池的使用避不開,儘早理解線程池的原理以及使用注意點對今後工作起到好的作用。合理配置線程池,對線程池進行監控避免線程池配置錯誤和有個錯誤可以快速定位。