Java線程池

線程池的定義

Java中的線程池是運用場景最多的併發框架,幾乎所有需要異步或併發執行任務的程序都可以使用線程池。線程池就是將線程進行池化,需要運行任務時從池中拿一個線程來執行,執行完畢,線程放回池中。
在開發過程中,合理地使用線程池能夠帶來3個好處。
第一:降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。

線程池各個參數的含義

ThreadPoolExecutor有四個構造函數,但最終調用的都是同一個:

 public ThreadPoolExecutor(
	int corePoolSize,//線程池核心線程數量
	int maximumPoolSize,//線程池最大數量
	long keepAliveTimet,//空閒線程存活時間
	TimeUnit unit,//時間單位
	BlockingQueue<Runnable> workQueue,//線程池使用的緩衝隊列
	ThreadFactory threadFactory,//線程池創建線程使用的工廠類
	RejectedExecutionHandler handler//線程池對拒絕任務的處理策略
	)

線程池的工作機制

在這裏插入圖片描述

這裏有一點需要注意是:當核心線程已滿,隊列未滿的時候,這時新來的請求會加入隊列等待;而此時假如隊列恰好滿了,這時新來的任務就會創建非核心線程執行任務。這樣就會出現一個問題,前面來的任務還在隊列中等待,而後來的任務卻在非核心線程中得到執行!

Executors創建ThreadPoolExecutor的問題

Executors創建ThreadPoolExecutor對象的方法有三種:

  • Executors#newCachedThreadPool => 創建可緩存的線程池
  • Executors#newSingleThreadExecutor => 創建單線程的線程池
  • Executors#newFixedThreadPool => 創建固定長度的線程池

Executors#newCachedThreadPool方法

public static ExectorService new CachedThreadPool(){
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}

CachedThreadPool是一個根據需要創建新線程的線程池,當一個任務提交時,corePoolSize爲0不創建核心線程,SynchronousQueue是一個不存儲元素的隊列,可以理解爲隊列永遠是滿的,因此最終會創建非核心線程來執行任務。非核心線程空閒60s後會被回收。
因爲Integer.MAX_VALUE非常大,可以認爲是可以無限創建線程的,在資源有限的情況下容易引起OOM異常

Executors#newSingleThreadExecutor方法

public static ExecutorService newSingleThreadExecutor(){
	return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlokingQueue<Runnable>()));
}

SingleThreadExecutor是單線程線程池,只有一個核心線程。當提交一個任務時,首先創建一個核心線程,再有就會放入隊列,LinkedBlockingQueue是長度爲Integer.MAX_VALUE的隊列,基本可視爲無界隊列,所以無論maximumPoolSize和keepAliveTime參數是多少,都可視爲無效,因爲所有的任務都會放入隊列中。在資源有限的時候容易引起OOM異常

Executors#newFixedThreadPool方法

public static ExecutorService newFixedThreadPool(int nThreads){
	return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDES, new LinkedBlockingQueue<Runnable>());
}

它和SingleThreadExecutor唯一區別就是核心線程的數量!由於使用的是LinkedBlockingQueue,在資源有限的時候容易引起OOM異常

如何合理配置線程池

合理配置線程池大小
性質不同的任務可以用不同規模的線程池分開處理。CPU密集型任務應配置儘可能小的線程,如配置Ncpu+1個線程的線程池。由於IO密集型任務線程並不是一直在執行任務,則應配置儘可能多的線程,如2*Ncpu。

避免使用無界隊列
不要使用Executors.newXXXThreadPool()快捷方法創建線程池,因爲它們不是可創建線程太多就是隊列可緩存太多線程,都可能造成OOM。所以最好是我們自己使用ThreadPoolExecutor的構造方法手動指定隊列的最大長度。
常用阻塞隊列有:

  • ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列。
  • LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列。
  • PriorityBlockingQueue:一個支持優先級排序的無界阻塞隊列。
  • DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。
  • SynchronousQueue:一個不存儲元素的阻塞隊列。
  • LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
  • LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。
ExecutorService executorService = new ThreadPoolExecutor(2, 2, 
                0, TimeUnit.SECONDS, 
                new ArrayBlockingQueue<>(512), // 使用有界隊列,避免OOM
                new ThreadPoolExecutor.DiscardPolicy());
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章