您好,登錄后才能下訂單哦!
Java中怎么創建線程池,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
Executor框架
Executor是一套線程池管理框架,接口里只有一個方法execute,執行Runnable任務。ExecutorService接口擴展了Executor,添加了線程生命周期的管理,提供任務終止、返回任務結果等方法。AbstractExecutorService實現了ExecutorService,提供例如submit方法的默認實現邏輯。
然后到今天的主題ThreadPoolExecutor,繼承了AbstractExecutorService,提供線程池的具體實現。
構造方法
下面是ThreadPoolExecutor最普通的構造函數,最多有七個參數。具體代碼不貼了,只是一些參數校驗和設置的語句。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {}
corePoolSize是線程池的目標大小,即是線程池剛剛創建起來,還沒有任務要執行時的大小。maximumPoolSize是線程池的最大上限。keepAliveTime是線程的存活時間,當線程池內的線程數量大于corePoolSize,超出存活時間的空閑線程就會被回收。unit就不用說了,剩下的三個參數看后文的分析。
預設的定制線程池
ThreadPoolExecutor預設了一些已經定制好的線程池,由Executors里的工廠方法創建。下面分析newSingleThreadExecutor、newFixedThreadPool、newCachedThreadPool的創建參數。
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}
newFixedThreadPool的corePoolSize和maximumPoolSize都設置為傳入的固定數量,keepAliveTim設置為0。線程池創建后,線程數量將會固定不變,適合需要線程很穩定的場合。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}
newSingleThreadExecutor是線程數量固定為1的newFixedThreadPool版本,保證池內的任務串行。注意到返回的是FinalizableDelegatedExecutorService,來看看源碼:
static class FinalizableDelegatedExecutorServiceextends DelegatedExecutorService {FinalizableDelegatedExecutorService(ExecutorService executor) {super(executor);}protected void finalize() {super.shutdown();}}
FinalizableDelegatedExecutorService繼承了DelegatedExecutorService,僅僅在gc時增加關閉線程池的操作,再來看看DelegatedExecutorService的源碼:
static class DelegatedExecutorService extends AbstractExecutorService {private final ExecutorService e;DelegatedExecutorService(ExecutorService executor) { e = executor; }public void execute(Runnable command) { e.execute(command); }public void shutdown() { e.shutdown(); }public List<Runnable> shutdownNow() { return e.shutdownNow(); }public boolean isShutdown() { return e.isShutdown(); }public boolean isTerminated() { return e.isTerminated(); }//...}
代碼很簡單,DelegatedExecutorService包裝了ExecutorService,使其只暴露出ExecutorService的方法,因此不能再配置線程池的參數。本來,線程池創建的參數是可以調整的,ThreadPoolExecutor提供了set方法。使用newSingleThreadExecutor目的是生成單線程串行的線程池,如果還能配置線程池大小,那就沒意思了。
Executors還提供了unconfigurableExecutorService方法,將普通線程池包裝成不可配置的線程池。如果不想線程池被不明所以的后人修改,可以調用這個方法。
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
newCachedThreadPool生成一個會緩存的線程池,線程數量可以從0到Integer.MAX_VALUE,超時時間為1分鐘。線程池用起來的效果是:如果有空閑線程,會復用線程;如果沒有空閑線程,會新建線程;如果線程空閑超過1分鐘,將會被回收。
newScheduledThreadPool
newScheduledThreadPool將會創建一個可定時執行任務的線程池。這個不打算在本文展開,后續會另開文章細講。
等待隊列
newCachedThreadPool的線程上限幾乎等同于無限,但系統資源是有限的,任務的處理速度總有可能比不上任務的提交速度。因此,可以為ThreadPoolExecutor提供一個阻塞隊列來保存因線程不足而等待的Runnable任務,這就是BlockingQueue。
JDK為BlockingQueue提供了幾種實現方式,常用的有:
ArrayBlockingQueue:數組結構的阻塞隊列 LinkedBlockingQueue:鏈表結構的阻塞隊列 PriorityBlockingQueue:有優先級的阻塞隊列 SynchronousQueue:不會存儲元素的阻塞隊列
newFixedThreadPool和newSingleThreadExecutor在默認情況下使用一個無界的LinkedBlockingQueue。要注意的是,如果任務一直提交,但線程池又不能及時處理,等待隊列將會無限制地加長,系統資源總會有消耗殆盡的一刻。所以,推薦使用有界的等待隊列,避免資源耗盡。但解決一個問題,又會帶來新問題:隊列填滿之后,再來新任務,這個時候怎么辦?后文會介紹如何處理隊列飽和。
newCachedThreadPool使用的SynchronousQueue十分有趣,看名稱是個隊列,但它卻不能存儲元素。要將一個任務放進隊列,必須有另一個線程去接收這個任務,一個進就有一個出,隊列不會存儲任何東西。因此,SynchronousQueue是一種移交機制,不能算是隊列。newCachedThreadPool生成的是一個沒有上限的線程池,理論上提交多少任務都可以,使用SynchronousQueue作為等待隊列正合適。
飽和策略
當有界的等待隊列滿了之后,就需要用到飽和策略去處理,ThreadPoolExecutor的飽和策略通過傳入RejectedExecutionHandler來實現。如果沒有為構造函數傳入,將會使用默認的defaultHandler。
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();public static class AbortPolicy implements RejectedExecutionHandler {public AbortPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());}}
AbortPolicy是默認的實現,直接拋出一個RejectedExecutionException異常,讓調用者自己處理。除此之外,還有幾種飽和策略,來看一下:
public static class DiscardPolicy implements RejectedExecutionHandler {public DiscardPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}}
DiscardPolicy的rejectedExecution直接是空方法,什么也不干。如果隊列滿了,后續的任務都拋棄掉。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {public DiscardOldestPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {e.getQueue().poll();e.execute(r);}}}
DiscardOldestPolicy會將等待隊列里最舊的任務踢走,讓新任務得以執行。
public static class CallerRunsPolicy implements RejectedExecutionHandler {public CallerRunsPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {r.run();}}}
最后一種飽和策略是CallerRunsPolicy,它既不拋棄新任務,也不拋棄舊任務,而是直接在當前線程運行這個任務。當前線程一般就是主線程啊,讓主線程運行任務,說不定就阻塞了。如果不是想清楚了整套方案,還是少用這種策略為妙。
ThreadFactory
每當線程池需要創建一個新線程,都是通過線程工廠獲取。如果不為ThreadPoolExecutor設定一個線程工廠,就會使用默認的defaultThreadFactory:
public static ThreadFactory defaultThreadFactory() {return new DefaultThreadFactory();}static class DefaultThreadFactory implements ThreadFactory {private static final AtomicInteger poolNumber = new AtomicInteger(1);private final ThreadGroup group;private final AtomicInteger threadNumber = new AtomicInteger(1);private final String namePrefix;DefaultThreadFactory() {SecurityManager s = System.getSecurityManager();group = (s != null) ? s.getThreadGroup() :Thread.currentThread().getThreadGroup();namePrefix = "pool-" +poolNumber.getAndIncrement() +"-thread-";}public Thread newThread(Runnable r) {Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(),0);if (t.isDaemon())t.setDaemon(false);if (t.getPriority() != Thread.NORM_PRIORITY)t.setPriority(Thread.NORM_PRIORITY);return t;}}
平時打印線程池里線程的name時,會輸出形如pool-1-thread-1之類的名稱,就是在這里設置的。這個默認的線程工廠,創建的線程是普通的非守護線程,如果需要定制,實現ThreadFactory后傳給ThreadPoolExecutor即可。
不看代碼不總結不會知道,光是線程池的創建就可以引出很多學問。別看平時創建線程池是一句代碼的事,其實ThreadPoolExecutor提供了很靈活的定制方法。
關于Java中怎么創建線程池問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。