今天對五種常見的java內置線程池進行講解。
線程使用的demo
public static void cache() {ExecutorService pool = Executors.newCachedThreadPool();long start = System.currentTimeMillis();pool.execute(() -> {int sum = 0;for (int i = 0; i < 10; i++) {sum = (int) Math.sqrt(i * i - 1 + i);System.out.println(sum);}});System.out.println("cache: " + (System.currentTimeMillis() - start));}
newCachedThreadPool
- 重用之前的線程
- 適合執行許多短期異步任務的程序。
- 調用 execute() 將重用以前構造的線程
- 如果沒有可用的線程,則創建一個新線程并添加到池中
- 默認為60s未使用就被終止和移除
- 長期閑置的池將會不消耗任何資源
源碼:
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
通過源碼可以看出底層調用的是ThreadPoolExecutor方法,傳入一個同步的阻塞隊列實現緩存。
下面說一下ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}
通過源碼可以看出,我們可以傳入線程池的核心線程數(最小線程數),最大線程數量,保持時間,時間單位,阻塞隊列這些參數,最大線程數設置為jvm可用的cpu數量為最佳實踐
newWorkStealingPool
- 獲取當前可用的線程數量進行創建作為并行級別
- 使用ForkJoinPool
源碼:
public static ExecutorService newWorkStealingPool() {return new ForkJoinPool(Runtime.getRuntime().availableProcessors(),ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true);}
通過源碼可以看出底層調用的是ForkJoinPool線程池
下面說一下ForkJoinPool
public ForkJoinPool(int parallelism,ForkJoinWorkerThreadFactory factory,UncaughtExceptionHandler handler,boolean asyncMode) {this(checkParallelism(parallelism),checkFactory(factory),handler,asyncMode ? FIFO_QUEUE : LIFO_QUEUE,"ForkJoinPool-" + nextPoolId() + "-worker-");checkPermission();}
使用一個無限隊列來保存需要執行的任務,可以傳入線程的數量,不傳入,則默認使用當前計算機中可用的cpu數量,使用分治法來解決問題,使用fork()和join()來進行調用
newSingleThreadExecutor
- 在任何情況下都不會有超過一個任務處于活動狀態
- 與newFixedThreadPool(1)不同是不能重新配置加入線程,使用FinalizableDelegatedExecutorService進行包裝
- 能保證執行順序,先提交的先執行。
- 當線程執行中出現異常,去創建一個新的線程替換之
源碼:
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}
newFixedThreadPool
- 創建重用固定數量線程的線程池,不能隨時新建線程
- 當所有線程都處于活動狀態時,如果提交了其他任務,
他們將在隊列中等待一個線程可用 - 線程會一直存在,直到調用shutdown
源碼:
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}
newScheduledThreadPool
- 設定延遲時間,定期執行
- 空閑線程會進行保留
源碼:
return new ScheduledThreadPoolExecutor(corePoolSize);
通過源碼可以看出底層調用的是一個ScheduledThreadPoolExecutor,然后傳入線程數量
下面來介紹一下ScheduledThreadPoolExecutor
public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());}
通過源碼可以看出底層調用了ThreadPoolExecutor,維護了一個延遲隊列,可以傳入線程數量,傳入延時的時間等參數,下面給出一個demo
public static void main(String[] args) {ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);for (int i = 0; i < 15; i = i + 5) {pool.schedule(() -> System.out.println("我被執行了,當前時間" + new Date()), i, TimeUnit.SECONDS);}pool.shutdown();}
執行結果
我被執行了,當前時間Fri Jan 12 11:20:41 CST 2018
我被執行了,當前時間Fri Jan 12 11:20:46 CST 2018
我被執行了,當前時間Fri Jan 12 11:20:51 CST 2018
有的小伙伴可能會用疑問,為什么使用schedule()而不使用submit()或者execute()呢,下面通過源碼來分析
public void execute(Runnable command) {schedule(command, 0, NANOSECONDS);}public Future<?> submit(Runnable task) {return schedule(task, 0, NANOSECONDS);}
通過源碼可以發現這兩個方法都是調用的schedule(),而且將延時時間設置為了0,所以想要實現延時操作,需要直接調用schedule()
下面我們再來分析一下submit()和execute()的以及shutdown()和shutdownNow()的區別
- submit(),提交一個線程任務,可以接受回調函數的返回值嗎,適用于需要處理返回著或者異常的業務場景
- execute(),執行一個任務,沒有返回值
- shutdown(),表示不再接受新任務,但不會強行終止已經提交或者正在執行中的任務
- shutdownNow(),對于尚未執行的任務全部取消,正在執行的任務全部發出interrupt(),停止執行
五種線程池的適應場景
- newCachedThreadPool:用來創建一個可以無限擴大的線程池,適用于服務器負載較輕,執行很多短期異步任務。
- newFixedThreadPool:創建一個固定大小的線程池,因為采用無界的阻塞隊列,所以實際線程數量永遠不會變化,適用于可以預測線程數量的業務中,或者服務器負載較重,對當前線程數量進行限制。
- newSingleThreadExecutor:創建一個單線程的線程池,適用于需要保證順序執行各個任務,并且在任意時間點,不會有多個線程是活動的場景。
- newScheduledThreadPool:可以延時啟動,定時啟動的線程池,適用于需要多個后臺線程執行周期任務的場景。
- newWorkStealingPool:創建一個擁有多個任務隊列的線程池,可以減少連接數,創建當前可用cpu數量的線程來并行執行,適用于大耗時的操作,可以并行來執行
?
轉自:https://www.jianshu.com/p/135c89001b61