總結Java開發面試常問的問題,GitHub地址:github.com/zaiyunduan1…,持續更新中~,若是對你有幫助歡迎Starjava
線程池的基本思想是一種對象池,在程序啓動時就開闢一塊內存空間,裏面存放了衆多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣能夠避免反覆建立線程對象所帶來的性能開銷,節省了系統的資源。git
- 減小了建立和銷燬線程的次數,每一個工做線程均可以被重複利用,可執行多個任務。
- 運用線程池能有效的控制線程最大併發數,能夠根據系統的承受能力,調整線程池中工做線線程的數目,防止由於消耗過多的內存,而把服務器累趴下(每一個線程須要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。
- 對線程進行一些簡單的管理,好比:延時執行、定時循環執行的策略等,運用線程池都能進行很好的實現
一個線程池包括如下四個基本組成部分:github
講到線程池,要重點介紹java.uitl.concurrent.ThreadPoolExecutor類,ThreadPoolExecutor線程池中最核心的一個類,ThreadPoolExecutor在JDK中線程池經常使用類UML類關係圖以下:面試
咱們能夠經過ThreadPoolExecutor來建立一個線程池new ThreadPoolExecutor(corePoolSize, maximumPoolSize,keepAliveTime,
milliseconds,runnableTaskQueue, threadFactory,handler);
複製代碼
咱們能夠經過execute()或submit()兩個方法向線程池提交任務,不過它們有所不一樣緩存
threadsPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});
複製代碼
try {
Object s = future.get();
} catch (InterruptedException e) {
// 處理中斷異常
} catch (ExecutionException e) {
// 處理沒法執行任務異常
} finally {
// 關閉線程池
executor.shutdown();
}
複製代碼
咱們能夠經過shutdown()或shutdownNow()方法來關閉線程池,不過它們也有所不一樣bash
/** * 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. * 若是當前的線程數小於核心線程池的大小,根據現有的線程做爲第一個Worker運行的線程, * 新建一個Worker,addWorker自動的檢查當前線程池的狀態和Worker的數量, * 防止線程池在不能添加線程的狀態下添加線程 * * 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. * 若是線程入隊成功,而後仍是要進行double-check的,由於線程池在入隊以後狀態是可能會發生變化的 * * 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. * * 若是task不能入隊(隊列滿了),這時候嘗試增長一個新線程,若是增長失敗那麼當前的線程池狀態變化了或者線程池已經滿了 * 而後拒絕task */
int c = ctl.get();
//當前的Worker的數量小於核心線程池大小時,新建一個Worker。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//若是當前CorePool內的線程大於等於CorePoolSize,那麼將線程加入到BlockingQueue。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))//recheck防止線程池狀態的突變,若是突變,那麼將reject線程,防止workQueue中增長新線程
reject(command);
else if (workerCountOf(recheck) == 0)//上下兩個操做都有addWorker的操做,可是若是在workQueue.offer的時候Worker變爲0,
//那麼將沒有Worker執行新的task,因此增長一個Worker. addWorker(null, false);
}
//若是workQueue滿了,那麼這時候可能還沒到線程池的maxnum,因此嘗試增長一個Worker
else if (!addWorker(command, false))
reject(command);//若是Worker數量到達上限,那麼就拒絕此線程
}
複製代碼
新建線程 -> 達到核心數 -> 加入隊列 -> 新建線程(非核心) -> 達到最大數 -> 觸發拒絕策略服務器
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
複製代碼
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
複製代碼
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
複製代碼
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
複製代碼
- CachedThreadPool():可緩存線程池。
- FixedThreadPool():定長線程池。
- ScheduledThreadPool():
- SingleThreadExecutor():單線程化的線程池。
newCachedThreadPool建立一個可緩存線程池,若是線程池長度超過處理須要,可靈活回收空閒線程,若無可回收,則新建線程併發
public class ThreadPoolExecutorTest1 {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (Exception e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+":"+index);
}
});
}
}
}
複製代碼
newFixedThreadPool建立一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待,指定線程池中的線程數量和最大線程數量同樣,也就線程數量固定不變ide
示例代碼以下函數
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);// 每隔兩秒打印3個數
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(Thread.currentThread().getName()+":"+index);
//三個線程併發
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
複製代碼
newscheduledThreadPool建立一個定長線程池,支持定時及週期性任務執行。延遲執行示例代碼以下.表示延遲1秒後每3秒執行一次
public class ThreadPoolExecutorTest3 {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + ": delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);// 表示延遲1秒後每3秒執行一次
}
}
複製代碼
newSingleThreadExecutor建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行
public class ThreadPoolExecutorTest4 {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
System.out.println(Thread.currentThread().getName() + ":" + index);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
複製代碼
結果依次輸出,至關於順序執行各個任務。使用JDK自帶的監控工具來監控咱們建立的線程數量,運行一個不終止的線程,建立指定量的線程,來觀察
參數的設置跟系統的負載有直接的關係,下面爲系統負載的相關參數:
系統每秒有tasks個任務須要處理理,則每一個線程每鈔可處理threadtasks個任務。,則須要的線程數爲:tasks/threadtasks,即tasks/threadtasks個線程數。
假設系統每秒任務數爲100 ~ 1000,每一個線程每鈔可處理10個任務,則須要100 / 10至1000 / 10,即10 ~ 100個線程。那麼corePoolSize應該設置爲大於10,具體數字最好根據8020原則,由於系統每秒任務數爲100 ~ 1000,即80%狀況下系統每秒任務數小於1000 * 20% = 200,則corePoolSize可設置爲200 / 10 = 20。
任務隊列的長度要根據核心線程數,以及系統對任務響應時間的要求有關。隊列長度能夠設置爲 全部核心線程每秒處理任務數 * 每一個任務響應時間 = 每秒任務總響應時間 ,即(corePoolSize*threadtasks)responsetime: (2010)*2=400,即隊列長度可設置爲400。
當系統負載達到最大值時,核心線程數已沒法按時處理完全部任務,這時就須要增長線程。每秒200個任務須要20個線程,那麼當每秒達到1000個任務時,則須要(tasks - queueCapacity)/ threadtasks 即(1000-400)/10,即60個線程,可將maxPoolSize設置爲60。
隊列長度設置過大,會致使任務響應時間過長,切忌如下寫法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
複製代碼
這其實是將隊列長度設置爲Integer.MAX_VALUE,將會致使線程數量永遠爲corePoolSize,不再會增長,當任務數量陡增時,任務響應時間也將隨之陡增。
當負載下降時,可減小線程數量,當線程的空閒時間超過keepAliveTime,會自動釋放線程資源。默認狀況下線程池中止多餘的線程並最少會保持corePoolSize個線程。
默認狀況下核心線程不會退出,可經過將該參數設置爲true,讓核心線程也退出。
線程池提供兩種關閉線程池方法:shutDown()和shutdownNow()
當線程池調用該方法時,線程池的狀態則馬上變成SHUTDOWN狀態。此時,則不能再往線程池中添加任何任務,不然將會拋出RejectedExecutionException異常。可是,此時線程池不會馬上退出,直到添加到線程池中的任務都已經處理完成,纔會退出。
根據JDK文檔描述,大體意思是:執行該方法,線程池的狀態馬上變成STOP狀態,並試圖中止全部正在執行的線程,再也不處理還在池隊列中等待的任務,固然,它會返回那些未執行的任務。
它試圖終止線程的方法是經過調用Thread.interrupt()方法來實現的,可是你們知道,這種方法的做用有限,若是線程中沒有sleep、wait、Condition、定時鎖等應用, interrupt()方法是沒法中斷當前的線程的。因此,ShutdownNow()並不表明線程池就必定當即就能退出,它可能必需要等待全部正在執行的任務都執行完成了才能退出。