從這一節(jié)開始正式進(jìn)入線程池的部分。其實(shí)整個(gè)體系已經(jīng)拖了很長的時(shí)間,因此后面的章節(jié)會加快速度,甚至只是一個(gè)半成品或者簡單化,以后有時(shí)間的慢慢補(bǔ)充、完善。
其實(shí)線程池是并發(fā)包里面很重要的一部分,在實(shí)際情況中也是使用很多的一個(gè)重要組件。
下圖描述的是線程池API的一部分。廣義上的完整線程池可能還包括Thread/Runnable、Timer/TimerTask等部分。這里只介紹主要的和高級的API以及架構(gòu)和原理。

大多數(shù)并發(fā)應(yīng)用程序是圍繞執(zhí)行任務(wù)(Task)進(jìn)行管理的。所謂任務(wù)就是抽象、離散的工作單元(unit of work)。把一個(gè)應(yīng)用程序的工作(work)分離到任務(wù)中,可以簡化程序的管理;這種分離還在不同事物間劃分了自然的分界線,可以方便程序在出現(xiàn)錯誤時(shí)進(jìn)行恢復(fù);同時(shí)這種分離還可以為并行工作提供一個(gè)自然的結(jié)構(gòu),有利于提高程序的并發(fā)性。[1]
并發(fā)執(zhí)行任務(wù)的一個(gè)很重要前提是拆分任務(wù)。把一個(gè)大的過程或者任務(wù)拆分成很多小的工作單元,每一個(gè)工作單元可能相關(guān)、也可能無關(guān),這些單元在一定程度上可以充分利用CPU的特性并發(fā)的執(zhí)行,從而提高并發(fā)性(性能、響應(yīng)時(shí)間、吞吐量等)。
所謂的任務(wù)拆分就是確定每一個(gè)執(zhí)行任務(wù)(工作單元)的邊界。理想情況下獨(dú)立的工作單元有最大的吞吐量,這些工作單元不依賴于其它工作單元的狀態(tài)、結(jié)果或者其他資源等。因此將任務(wù)盡可能的拆分成一個(gè)個(gè)獨(dú)立的工作單元有利于提高程序的并發(fā)性。
對于有依賴關(guān)系以及資源競爭的工作單元就涉及到任務(wù)的調(diào)度和負(fù)載均衡。工作單元的狀態(tài)、結(jié)果或者其他資源等有關(guān)聯(lián)的工作單元就需要有一個(gè)總體的調(diào)度者來協(xié)調(diào)資源和執(zhí)行順序。同樣在有限的資源情況下,大量的任務(wù)也需要一個(gè)協(xié)調(diào)各個(gè)工作單元的調(diào)度者。這就涉及到任務(wù)執(zhí)行的策略問題。
任務(wù)的執(zhí)行策略包括4W3H部分:
- 任務(wù)在什么(What)線程中執(zhí)行
- 任務(wù)以什么(What)順序執(zhí)行(FIFO/LIFO/優(yōu)先級等)
- 同時(shí)有多少個(gè)(How Many)任務(wù)并發(fā)執(zhí)行
- 允許有多少個(gè)(How Many)個(gè)任務(wù)進(jìn)入執(zhí)行隊(duì)列
- 系統(tǒng)過載時(shí)選擇放棄哪一個(gè)(Which)任務(wù),如何(How)通知應(yīng)用程序這個(gè)動作
- 任務(wù)執(zhí)行的開始、結(jié)束應(yīng)該做什么(What)處理
在后面的章節(jié)中會詳細(xì)分寫這些策略是如何實(shí)現(xiàn)的。我們先來簡單回答些如何滿足上面的條件。
- 首先明確一定是在Java里面可以供使用者調(diào)用的啟動線程類是Thread。因此Runnable或者Timer/TimerTask等都是要依賴Thread來啟動的,因此在ThreadPool里面同樣也是靠Thread來啟動多線程的。
- 默認(rèn)情況下Runnable接口執(zhí)行完畢后是不能拿到執(zhí)行結(jié)果的,因此在ThreadPool里就定義了一個(gè)Callable接口來處理執(zhí)行結(jié)果。
- 為了異步阻塞的獲取結(jié)果,F(xiàn)uture可以幫助調(diào)用線程獲取執(zhí)行結(jié)果。
- Executor解決了向線程池提交任務(wù)的入口問題,同時(shí)ScheduledExecutorService解決了如何進(jìn)行重復(fù)調(diào)用任務(wù)的問題。
- CompletionService解決了如何按照執(zhí)行完畢的順序獲取結(jié)果的問題,這在某些情況下可以提高任務(wù)執(zhí)行的并發(fā),調(diào)用線程不必在長時(shí)間任務(wù)上等待過多時(shí)間。
- 顯然線程的數(shù)量是有限的,而且也不宜過多,因此合適的任務(wù)隊(duì)列是必不可少的,BlockingQueue的容量正好可以解決此問題。
- 固定任務(wù)容量就意味著在容量滿了以后需要一定的策略來處理過多的任務(wù)(新任務(wù)),RejectedExecutionHandler正好解決此問題。
- 一定時(shí)間內(nèi)阻塞就意味著有超時(shí),因此TimeoutException就是為了描述這種現(xiàn)象。TimeUnit是為了描述超時(shí)時(shí)間方便的一個(gè)時(shí)間單元枚舉類。
- 有上述問題就意味了配置一個(gè)合適的線程池是很復(fù)雜的,因此Executors默認(rèn)的一些線程池配置可以減少這個(gè)操作。
線程池的基本策略大致就這些,從下一節(jié)開始就從線程池的基本原理和執(zhí)行方法開始描述。
[1] Java Concurrency in Practice