why線程池
上一篇中有介紹了線程與進程的區別【線程與進程】;JDK中已經提供了Thread類和runnable接口來創建和啓動一個線程了, 爲什麼還需線程池呢? 因爲創建和銷燬線程是需要有成本的,如果頻繁的去做這個事情,那麼會很浪費CPU的資源,進而影響之下的效率;極端情況下也可能引起OOM,導致系統掛掉。我們需要一個管理線程的池子來管理我們的任務,提高CPU的使用率同時又能夠解決資源浪費的弊端。
ThreadPoolExecutor
在著名的concurrent包下有一個ThreadPoolExecutor類,可以看下里面的代碼邏輯。我們看下這個類的構造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
參數說明:
1.corePoolSize:*corePoolSize the number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set; 就是說核心線程數在一直保留在池子中,即使沒有任務可執行; 如果設置了allowCoreThreadTimeOut,那麼核心線程會超時一定時間滯後銷燬掉;*
2.maximumPoolSize: the maximum number of threads to allow in the pool; 線程池中最大的線程數目。
3.keepAliveTime:when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating; 這是針對超過核心線程數的線程任務來說的, 如果超過一定時間,那麼就會被回收。
4.BlockingQueue workQueue: workQueue the queue to use for holding tasks before they are
executed. This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method 這個參數設置任務隊列的類型. 常見的類型:ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue
5.RejectedExecutionHandler:handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached;拒絕策略,當任務數到最大的隊列大小後,定義任務處理的策略;總共有5種拒絕策略。
AbortPolicy:默認策略,不執行任務,直接拋出異常。
DiscardPolicy:直接拋棄,不執行
DiscardOldestPolicy:拋棄最老的任務,再執行
CallerRunsPolicy:由調用者本身去啓線程執行。
線程池工作流程
具體參見上圖:
a.任務數量是否達到核心線程大小:如果沒有,則執行任務,
b.任務數量達到了核心,則將任務push入隊列等待執行。
c.隊列已經滿了,則新建線程執行,直到最大線程數
d.如果任務隊列滿了, 並且達到最大線程數,則執行拒絕策略。
常見線程池
如果我們不想自己去配置線程池參數,JDK幫我們定義了幾個比較有特色的池子,我們可以直接拿來用。
CachedThreadPool: These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks.
優勢:短生命週期的異步任務時,有優勢,可以提高程序的性能;可以看到是用的SynchronousQueue。可緩存的池子,如果不存在則創建一個新的池子,如果60秒之後未有任務,則超時移除。
NewFixedThreadPool:這個會重複使用固定數量的線程 , 採用的是無邊界隊列,但是需要注意內存溢出的問題。
如果線程在執行過程中因爲錯誤而中止,新線程會替代它的位置來執行後續的任務。
SingleThreadPool:只會使用單個工作線程來執行一個無邊界的隊列。如果線程遇到錯誤中止,它是無法使用替代線程的。
/**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available. These pools will typically improve the performance
* of programs that execute many short-lived asynchronous tasks.
* Calls to {@code execute} will reuse previously constructed
* threads if available. If no existing thread is available, a new
* thread will be created and added to the pool. Threads that have
* not been used for sixty seconds are terminated and removed from
* the cache. Thus, a pool that remains idle for long enough will
* not consume any resources. Note that pools with similar
* properties but different details (for example, timeout parameters)
* may be created using {@link ThreadPoolExecutor} constructors.
*
* @return the newly created thread pool
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue. At any point, at most
* {@code nThreads} threads will be active processing tasks.
* If additional tasks are submitted when all threads are active,
* they will wait in the queue until a thread is available.
* If any thread terminates due to a failure during execution
* prior to shutdown, a new one will take its place if needed to
* execute subsequent tasks. The threads in the pool will exist
* until it is explicitly {@link ExecutorService#shutdown shutdown}.
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
* Creates an Executor that uses a single worker thread operating
* off an unbounded queue. (Note however that if this single
* thread terminates due to a failure during execution prior to
* shutdown, a new one will take its place if needed to execute
* subsequent tasks.) Tasks are guaranteed to execute
* sequentially, and no more than one task will be active at any
* given time. Unlike the otherwise equivalent
* {@code newFixedThreadPool(1)} the returned executor is
* guaranteed not to be reconfigurable to use additional threads.
*
* @return the newly created single-threaded Executor
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
開發實踐tips
1.對於任務負載比較高的應用,不合適用無界隊列,會存在內存溢出的風險以及高的延遲; 所以最好將隊列根據實際情況設置成有邊界的隊列。
2.根據場景使用合適的拒絕策略;以如果選用該策略,請注意異常的獲取在execute任務的時候。
3.負載比較低的應用,可以直接用newFixTheadPool,大小可根據cpu核數確定。