系統啟動一個線程的成本是比較高,使用線程池可以很好地提高性能,尤其是當程序中需要創建大量生存期很短暫的線程時
線程池在系統啟動時即創建大量空閑線程,將一個Runnable、Callable對象—–>傳給線程池—–>線程池啟動里面的一個線程來執行它們的run()或者call()方法———->當線程執行體執行完成后,線程并不會死亡,而是再次返回線程池成為空閑狀態,等待下一個Runnable、Callable對象的run()或者call()方法
Java8改進的線程池 Executors工廠類Java5開始,Java內建支持線程池。Executors工廠類來產生線程池,該工廠類包含如下幾個靜態工廠方法來創建線程池:
ExecutorService newCachedThreadPool():創建一個具有緩存功能的線程池,系統根據需要創建線程,這些線程將會被緩存在線程池中
ExecutorService newFixedThreadPool(int nThreads):創建一個可重用的、具有nThread個固定的線程的線程池
ExecutorService newSingleThreadExecutor():創建包含一個只有單線程的線程池,相當于調用newFixedThreadPool(1)
ScheduledExecutorService newScheduledThreadPool(int corePoolSize):創建具有指定線程數量的線程池,可以在指定延遲后執行線程任務。corePoolSize指池中所保存的線程數,即使線程是空閑的也被保存在線程池內
ScheduledExecutorService newSingleThreadScheduledExecutor():創建只有一個線程的線程池,可以指定延遲后執行線程任務
ExecutorService newWorkStealingPool(int parallelism):創建持有足夠的線程的線程池來支持給定的并行級別,該方法還會使用多個隊列來減少競爭
ExecutorService newWorkStealingPool():該方法可以看做是前一個方法的簡本,并行級別不需要用戶手工指定,是根據計算機CPU個數自動生成的,如果當前機器有6個CPU,則調用該方法時并行級別被設為6
前三個方法返回一個ExecutorService對象,代表一個線程池,可以執行Runnable對象和Callable對象所代表的線程;中間兩個方法返回一個ScheduledExecutorService對象,它是ExecutorService的子類,可以在指定延遲后執行線程任務;最后兩個方法生成的work stealing池,相當于后臺線程池,如果所有的前臺線程都死亡了,work stealig池中的線程也會自動死亡
ExecutorServiceExecutorService代表盡快執行線程的線程池(只要線程中有空閑線程就立即執行線程任務),程序只要將一個Runnable對象或Callable對象(代表線程任務)提交給該線程池,該線程池就會盡快執行該任務
ExecutorService里提供了如下3個方法:
Future> submit(Runnable task):將一個Runnable對象提交給指定的線程池,線程池將在有空閑線程時執行Runnable對象代表的任務。其中Future對象代表Runnable任務的返回值——但是run()方法沒有返回值,所以Future對象將在run()方法執行結束后返回null。但可以調用Future的isDone()、isCancelled()方法來獲得Runnable對象的執行狀態
ScheduledExecutorService代表可在指定延遲后或周期性地執行線程任務的線程池,這提供了如下4個方法:
ScheduledFuture
ScheduledFuture> schedule(Runnable command, long delay, TimeUnit unit):指定Command任務將在delay延遲后執行
ScheduledFuture> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):指定command任務將在delay延遲后執行,而且以設定頻率重復執行。即initialDelay后開始執行,依次在initialDelay + period、initialDelay + 2*period...處重復執行
ScheduledFuture> scheduleWithFixedDelay(Runnable command, long initialDelay,long delay,TimeUnit unit):創建并執行一個在給定初始延遲后首次啟動的定期操作,隨后在每一次執行終止和下一次執行開始之間都存在給定的延遲。如果任務在任一次執行時遇到異常,就會取消后續執行,否則,只能通過程序來顯式取消或終止該任務
使用線程池來執行線程任務當用完一個線程池后,應該調用該線程池的shutdown()方法,該方法將啟動線程池的關閉序列,調用shutdown()方法后的線程池不再接收新任務,但會將以前所有已提交的任務執行完成。當線程池中的所有任務都執行完成后,池中的所有線程都會死亡;另外也可以調用線程池的shutdownNow()方法來關閉線程池,該方法試圖停止所有正在執行的活動任務,暫停處理正在等待的任務,并返回等待的任務列表
使用線程池執行線程任務的步驟如下:
調用Executor類的靜態工廠方法創建一個ExecutorService對象,該對象代表一個線程池
創建Runnable或Callable接口實現類的實例,作為線程執行任務
調用ExcutorService的submit方法來提交Runnable或Callable實例
當不想提交任何任務時調用ExcutorService的shutdown()方法來關閉線程池
import java.util.concurrent.*; public class ThreadPoolTest { public static void main(String[] args) throws Exception { // 創建足夠的線程來支持4個CPU并行的線程池 // 創建一個具有固定線程數(6)的線程池 ExecutorService pool = Executors.newFixedThreadPool(6); // 使用Lambda表達式創建Runnable對象 Runnable target = () -> { for (int i = 0; i < 100 ; i++ ) { System.out.println(Thread.currentThread().getName() + "的i值為:" + i); } }; // 向線程池中提交兩個線程 pool.submit(target); pool.submit(target); // 關閉線程池 pool.shutdown(); } }Java8增強的ForkJoinPool
Java7提供了ForkJoinPool支持將一個任務拆分成多個“小任務”并行計算,再把多個“小任務”的結果合并成總的計算結果。ForkJoinPool是ExecutorService的實現類,是一種特殊的線程池
ForkJoinService提供了如下兩個常用的構造器:
ForkJoinPool(int parallelism):創建一個包含parallelism個并行線程的ForkJoinPool
ForkJoinPool():以Runtime.availableProcessors()方法的返回值作為parallelism參數來創建ForkJoinPool
Jav增加通用池功能,由如下兩個靜態方法提供通用池功能:
ForkJoinPoll commonPool():該方法返回一個通用池,通用池的運行狀態不受shutDown()或shutdownNow()方法的影響。但如果程序直接調用System.exit(0);來終止虛擬機,通用池以及通用池中正在執行的任務都會被自動終止
int getCommonPollParallelism():該方法返回通用池的并行級別
創建ForkJoinPool實例可調用ForkJoinPool的submit(ForkJoinTask task)或invoke(ForkJoinTask task)方法來執行指定任務了。其中ForkJoinTask代表一個可以并行、合并的任務
ForkJoinTask是一個抽象類,它有兩個抽象子類:RecursiveAction和RecursiveTask。其中RecursiveTask代表有返回值的任務,RecursiveAction代表沒有返回值的任務
線程池工具類的類圖:
簡單打印0~500的數值:
import java.util.concurrent.*; // 繼承RecursiveAction來實現"可分解"的任務 class PrintTask extends RecursiveAction { // 每個“小任務”只最多只打印50個數 private static final int THRESHOLD = 50; private int start; private int end; // 打印從start到end的任務 public PrintTask(int start, int end) { this.start = start; this.end = end; } @Override protected void compute() { // 當end與start之間的差小于THRESHOLD時,開始打印 if(end - start < THRESHOLD) { for (int i = start ; i < end ; i++ ) { System.out.println(Thread.currentThread().getName() + "的i值:" + i); } } else { // 如果當end與start之間的差大于THRESHOLD時,即要打印的數超過50個 // 將大任務分解成兩個小任務。 int middle = (start + end) / 2; PrintTask left = new PrintTask(start, middle); PrintTask right = new PrintTask(middle, end); // 并行執行兩個“小任務” left.fork(); right.fork(); } } } public class ForkJoinPoolTest { public static void main(String[] args) throws Exception { ForkJoinPool pool = new ForkJoinPool(); // 提交可分解的PrintTask任務 pool.submit(new PrintTask(0 , 500)); pool.awaitTermination(2, TimeUnit.SECONDS); // 關閉線程池 pool.shutdown(); } }
程序實現了對指定打印任務的分解,分解后的任務分別調用fork()方法開始并行執行。ForkJoinPool啟動了4個線程來執行打印任務
對一個長度為100的數值的元素值進行累加:
import java.util.concurrent.*; import java.util.*; // 繼承RecursiveTask來實現"可分解"的任務 class CalTask extends RecursiveTask{ // 每個“小任務”只最多只累加20個數 private static final int THRESHOLD = 20; private int arr[]; private int start; private int end; // 累加從start到end的數組元素 public CalTask(int[] arr, int start, int end) { this.arr = arr; this.start = start; this.end = end; } @Override protected Integer compute() { int sum = 0; // 當end與start之間的差小于THRESHOLD時,開始進行實際累加 if(end - start < THRESHOLD) { for (int i = start ; i < end ; i++ ) { sum += arr[i]; } return sum; } else { // 如果當end與start之間的差大于THRESHOLD時,即要累加的數超過20個時 // 將大任務分解成兩個小任務。 int middle = (start + end) / 2; CalTask left = new CalTask(arr, start, middle); CalTask right = new CalTask(arr, middle, end); // 并行執行兩個“小任務” left.fork(); right.fork(); // 把兩個“小任務”累加的結果合并起來 return left.join() + right.join(); // ① } } } public class Sum { public static void main(String[] args) throws Exception { int[] arr = new int[100]; Random rand = new Random(); int total = 0; // 初始化100個數字元素 for (int i = 0, len = arr.length; i < len ; i++ ) { int tmp = rand.nextInt(20); // 對數組元素賦值,并將數組元素的值添加到sum總和中。 total += (arr[i] = tmp); } System.out.println(total); // 創建一個通用池 ForkJoinPool pool = ForkJoinPool.commonPool(); // 提交可分解的CalTask任務 Future future = pool.submit(new CalTask(arr, 0, arr.length)); System.out.println(future.get()); // 關閉線程池 pool.shutdown(); } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66740.html
摘要:當活動線程核心線程非核心線程達到這個數值后,后續任務將會根據來進行拒絕策略處理。線程池工作原則當線程池中線程數量小于則創建線程,并處理請求。當線程池中的數量等于最大線程數時默默丟棄不能執行的新加任務,不報任何異常。 spring-cache使用記錄 spring-cache的使用記錄,坑點記錄以及采用的解決方案 深入分析 java 線程池的實現原理 在這篇文章中,作者有條不紊的將 ja...
摘要:四種線程池的使用介紹的弊端及四種線程池的使用,線程池的作用線程池作用就是限制系統中執行線程的數量。相比,提供的四種線程池的好處在于重用存在的線程,減少對象創建消亡的開銷,性能佳。延遲執行描述創建一個定長線程池,支持定時及周期性任務執行。 java 四種線程池的使用 介紹new Thread的弊端及Java四種線程池的使用 1,線程池的作用 線程池作用就是限制系統中執行線程的數量。 ...
摘要:高并發系列第篇文章。簡單的說,在使用了線程池之后,創建線程變成了從線程池中獲取一個空閑的線程,然后使用,關閉線程變成了將線程歸還到線程池。如果調用了線程池的方法,線程池會提前把核心線程都創造好,并啟動線程池允許創建的最大線程數。 java高并發系列第18篇文章。 本文主要內容 什么是線程池 線程池實現原理 線程池中常見的各種隊列 自定義線程創建的工廠 常見的飽和策略 自定義飽和策略 ...
閱讀 3822·2021-10-12 10:11
閱讀 3641·2021-09-13 10:27
閱讀 2546·2019-08-30 15:53
閱讀 1977·2019-08-29 18:33
閱讀 2197·2019-08-29 14:03
閱讀 1000·2019-08-29 13:27
閱讀 3322·2019-08-28 18:07
閱讀 774·2019-08-26 13:23