聲明:該文章中全部測試都是在JDK1.8的環境下。html
該文章是我在學習Java中的多線程這方面知識時,作的一些總結和記錄。java
若是有不正確的地方請你們多多包涵並做出指點,謝謝!編程
本文章有些內容參考並採用如下做品內容:數組
https://www.cnblogs.com/vipst...緩存
https://www.bilibili.com/vide...服務器
咱們知道CPU執行代碼都是一條一條順序執行的,因此本質上單核CPU的電腦不會在同一個時間點執行多個任務。可是在現實中,咱們在使用電腦的時候,能夠一邊聊微信,一邊聽歌。那這是怎麼作到的呢?其實操做系統多任務就是讓CPU對多個任務輪流交替執行。微信
舉個例子:在一個教室中同時坐着一年級,二年級,三年級三批學生,老師花一分鐘教一年級,花一分教二年級,花一分鐘教三年級,這樣輪流下去,看上去就像同時在教三個年級。多線程
一樣的,咱們使用電腦時,一邊聊微信,一邊聽歌也是這個原理。CPU讓微信執行0.001秒,讓音樂播放器執行0.001秒,在咱們看來,CPU就是在同時執行多個任務。異步
程序:被存儲在磁盤或其餘的數據存儲設備中的可執行文件,也就是一堆靜態的代碼。ide
進程:運行在內存中可執行程序實例
線程:線程是進程的一個實體,是CPU調度和分派的基本單位。
看着這些概念是否是很抽象,看的很不舒服,那麼下面我來用實例解釋一下以上幾個概念。
上面說到,咱們使用電腦時,能夠一邊聊微信,一邊聽歌。那這些軟件運行的整個過程是怎樣的呢?
若是咱們要用微信聊天,大部分的人都是雙擊擊桌面上的"微信"快捷方式,而雙擊這個快捷方式打開的其實是一個.exe文件,這個.exe文件就是一個程序,請看下圖:
我用一張圖來總結一下整個過程:
根據上面內容對於線程概念的瞭解,是否有個疑問,線程是怎麼建立出來的?帶着這個疑問咱們就來學習一下java中的線程是怎麼如何建立的。
java.lang.Thread類表明線程,任何線程都是Thread類(子類)的實例。
構造方法 | 功能介紹 |
---|---|
Thread() | 使用無參的方式構造對象 |
Thread(String name) | 根據參數指定的名稱來構造對象 |
Thread(Runnable target) | 根據參數指定的引用來構造對象,其中Runnable是個接口類型 |
Thread(Runnable target, String name) | 根據參數指定引用和名稱來構造對象 |
成員方法 | 功能介紹 |
---|---|
run() | 1.使用Runnable引用構造線程對象,調用該方法時最終調用接口中的版本 2.沒有使用Runnable引用構造線程對象,調用該方法時則啥也不作 |
start() | 用於啓動線程,Java虛擬機會自動調用該線程的run方法 |
自定義類繼承Thread類並根據本身的需求重寫run方法,而後在主類中建立該類的對象調用start方法,這樣就啓動了一個線程。
示例代碼以下:
//建立一個自定義類SubThreadRun繼承Thread類,做爲一個能夠備用的線程類 public class SubThreadRun extends Thread { @Override public void run() { //打印1~20的整數值 for (int i = 0; i < 20 ; i ++) { System.out.println("SubThreadRun線程中:" + i); } } } //在主方法中建立該線程並啓動 public class SubThreadRunTest { public static void main(String[] args) { //1.申明Thread類型的引用指向子類類型的對象 Thread t1 = new SubThreadRun(); //用於啓動線程,java虛擬機會自動調用該線程中的run方法 t1.start(); } } 輸出結果: SubThreadRun線程中:0 SubThreadRun線程中:2 。。。。。。 SubThreadRun線程中:19
到這裏你們會不會有如下一個疑問,看示例代碼:
public class SubThreadRunTest { public static void main(String[] args) { //1.申明Thread類型的引用指向子類類型的對象 Thread t1 = new SubThreadRun(); //調用run方法測試 t1.run(); } } 輸出結果: SubThreadRun線程中:0 SubThreadRun線程中:1 。。。。。。 SubThreadRun線程中:19
咱們不調用start方法,而是直接調用run方法,發現結果和調用start方法同樣,他們兩個方法的區別是啥呢?
咱們在主方法中也加入一個打印1-20的數,而後分別用run方法和start方法進行測試,實例代碼以下:
//使用run方法進行測試 public class SubThreadRunTest { public static void main(String[] args) { //1.申明Thread類型的引用指向子類類型的對象 Thread t1 = new SubThreadRun(); //2.調用run方法測試 t1.run(); //打印1~20的整數值 for (int i = 0; i < 20 ; i ++) { System.out.println("-----mian-----方法中:" + i); } } } 輸出結果: SubThreadRun線程中:0 SubThreadRun線程中:1 。。。。。。//都是SubThreadRun線程中 SubThreadRun線程中:19 -----mian-----方法中:0 -----mian-----方法中:1 。。。。。。//都是-----mian-----方法中 -----mian-----方法中:19 //使用start方法進行測試 public class SubThreadRunTest { public static void main(String[] args) { //1.申明Thread類型的引用指向子類類型的對象 Thread t1 = new SubThreadRun(); //用於啓動線程,java虛擬機會自動調用該線程中的run方法 t1.start(); //打印1~20的整數值 for (int i = 0; i < 20 ; i ++) { System.out.println("-----mian-----方法中:" + i); } } } 輸出結果: SubThreadRun線程中:0 -----mian-----方法中:0 SubThreadRun線程中:1 SubThreadRun線程中:2 -----mian-----方法中:1 。。。。。。//SubThreadRun線程和mian方法相互穿插 SubThreadRun線程中:19 -----mian-----方法中:19
從上面的例子可知:
第一種建立線程的方式咱們已經學會了,那這種建立線程的方式有沒有什麼缺陷呢?假如SubThreadRun類已經繼承了一個父類,這個時候咱們又要把該類做爲自定義線程類,若是仍是用繼承Thread類的方法來實現的話就違背了Java不可多繼承的概念。因此第二種建立方式就能夠避免這種問題。
自定義類實現Runnable接口並重寫run方法,建立該類的對象做爲實參來構造Thread類型的對象,而後使用Thread類型的對象調用start方法。
示例代碼以下:
//建立一個自定義類SubRunnableRun實現Runnable接口 public class SubRunnableRun implements Runnable { @Override public void run() { for (int i = 0; i < 20 ; i ++) { System.out.println("SubRunnableRun線程中:" + i); } } } //在主方法中建立該線程並啓動 public class SunRunnableRunTest { public static void main(String[] args) { //1.建立自定義類型的對象 SubRunnableRun srr = new SubRunnableRun(); //2.使用該對象做爲實參構造Thread類型的對象 Thread t1 = new Thread(srr); //3.使用Thread類型的對象調用start方法 t1.start(); //打印1~20之間的全部整數 for (int i = 0; i < 20 ; i ++) { System.out.println("-----mian-----方法中:" + i); } } } 輸出結果: SubRunnableRun線程中:0 -----mian-----方法中:0 SubRunnableRun線程中:1 SubRunnableRun線程中:2 -----mian-----方法中:1 。。。。。。//SubRunnableRun線程和mian方法相互穿插 SubRunnableRun線程中:19 -----mian-----方法中:19
到這裏你們會不會有一個疑問呢?
我在SunRunnableRunTest類的main方法中也實例化了Thread類,爲何該線程調用的是實現了Runnable接口的SubRunnableRun類中的run方法,而不是Thread類中的run方法。
爲了解決該疑問,咱們就進入Thread類去看一下源碼,源碼調用過程以下:
從上面的SunRunnableRunTest類中代碼可知,咱們建立線程調用的是Thread的有參構造方法,參數是Runnable類型的。
進入到Thread類中找到該有參構造方法,看到該構造方法調用init方法,而且把target參數繼續當參數傳遞過去。
轉到對應的init方法後,發現該init方法繼續調用另外一個重載的init方法,而且把target參數繼續當參數傳遞過去。
繼續進入到重載的init方法中,咱們發現,該方法中把參數中target賦值給成員變量target。
而後找到Thread類中的run方法,發現只要Thread的成員變量target存在,就調用target中的run方法。
經過查看源碼,咱們能夠知道爲何咱們建立的Thread類調用的是Runnable類中的run方法。
上面兩種建立線程的方式都須要單首創建一個類來繼承Thread類或者實現Runnable接口,並重寫run方法。而匿名內部類能夠不建立單獨的類而實現自定義線程的建立。
示例代碼以下:
public class ThreadNoNameTest { public static void main(String[] args) { //匿名內部類的語法格式:父類/接口類型 引用變量 = new 父類/接口類型 {方法的重寫}; //1.使用繼承加匿名內部類的方式建立並啓動線程 Thread t1 = new Thread() { @Override public void run() { System.out.println("繼承Thread類方式建立線程..."); } }; t1.start(); //2.使用接口加匿名內部類的方式建立並啓動線程 Runnable ra = new Runnable() { @Override public void run() { System.out.println("實現Runnable接口方式實現線程..."); } }; Thread t2 = new Thread(ra); t2.start(); } }
這兩個利用匿名內部類建立線程的方式還能繼續簡化代碼,尤爲是使用Runnable接口建立線程的方式,可使用Lambda表達式進行簡化。
示例代碼以下:
public class ThreadNoNameTest { public static void main(String[] args) { //1.使用繼承加匿名內部類簡化後的方式建立並啓動線程 new Thread() { @Override public void run() { System.out.println("簡化後繼承Thread類方式建立線程..."); } }.start(); //2.使用接口加匿名內部類簡化後的方式建立並啓動線程 new Thread(new Runnable() { @Override public void run() { System.out.println("簡化後實現Runnable接口方式實現線程..."); } }).start(); //2-1.對於接口加匿名內部建立線程,能夠繼續使用lambda表達式進行簡化。 //Java8開始支持lambda表達式:(形參列表) -> {方法體;} Runnable ra = () -> System.out.println("lambda表達式簡化實現Runnable接口方式實現線程..."); new Thread(ra).start(); //繼續簡化 new Thread(() -> System.out.println("lambda表達式簡化實現Runnable接口方式實現線程...")).start(); } }
經過上面幾個例子,咱們瞭解了兩種建立線程的方式,可是這兩種方式建立線程存在一個問題,就是run方法是沒有返回值的,因此若是咱們但願在線程結束以後給出一個結果,那就須要用到實現Callable接口建立線程。
(1)Callable接口
從Java5開始新增建立線程的第三中方式爲實現java.util.concurrent.Callable接口。
經常使用方法以下:
成員方法 | 功能介紹 |
---|---|
V call() | 計算結果,若是沒法計算結果,則拋出一個異常 |
咱們知道啓動線程只有建立一個Thread類並調用start方法,若是想讓線程啓動時調用到Callable接口中的call方法,就得用到FutureTask類。
(2)FutureTask類
java.util.concurrent.FutureTask類實現了RunnableFuture接口,RunnableFuture接口是Runnable和Future的綜合體,做爲一個Future,FutureTask能夠執行異步計算,能夠查看異步程序是否執行完畢,而且能夠開始和取消程序,並取得程序最終的執行結果,也能夠用於獲取調用方法後的返回結果。
經常使用方法以下:
構造方法 | 功能介紹 |
---|---|
FutureTask(Callable<v> callable) | 建立一個FutureTask,它將在運行時執行給定的Callable |
成員方法 | 功能介紹 |
---|---|
V get() | 獲取call方法計算的結果 |
從上面的概念能夠了解到FutureTask類的一個構造方法是以Callable爲參數的,而後FutureTask類是Runnable的子類,因此FutureTask類能夠做爲Thread類的參數。這樣的話咱們就能夠建立一個線程並調用Callable接口中的call方法。
實例代碼以下:
public class ThreadCallableTest implements Callable { @Override public Object call() throws Exception { //計算1 ~ 10000之間的累加和並打印返回 int sum = 0; for (int i = 0; i <= 10000; i ++) { sum += i; } System.out.println("call方法中的結果:" + sum); return sum; } public static void main(String[] args) { ThreadCallableTest tct = new ThreadCallableTest(); FutureTask ft = new FutureTask(tct); Thread t1 = new Thread(ft); t1.start(); Object ob = null; try { ob = ft.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("main方法中的結果:" + ob); } } 輸出結果: call方法中的結果:50005000 main方法中的結果:50005000
線程池的由來:
在講線程池以前,先來說一個故事,一個老闆開個飯店,但這個飯店很奇怪,每來一個顧客,老闆就去招一個新的大廚來作菜,等這個顧客走後,老闆直接把這個大廚辭了。若是是按這種經營方式的話,老闆天天就忙着招大廚,啥事都幹不了。
對於上面講的這個故事,咱們現實生活中的飯店老闆可沒有這麼蠢,他們都是在開店前就直接招了好幾個大廚候在廚房,等有顧客來了,直接作菜上菜,顧客走後,廚師留在後廚待命,這樣就把老闆解放了。
如今咱們來說一下線程池的由來:好比說服務器編程中,若是爲每個客戶都分配一個新的工做線程,而且當工做線程與客戶通訊結束時,這個線程被銷燬,這就須要頻繁的建立和銷燬工做線程。若是訪問服務器的客戶端過多,那麼會嚴重影響服務器的性能。
那麼咱們該如何解放服務器呢?對了,就像上面講的飯店老闆同樣,打造一個後廚,讓廚師候着。相對於服務器來講,就建立一個線程池,讓線程候着,等待客戶端的鏈接,等客戶端結束通訊後,服務器不關閉該線程,而是返回到線程中待命。這樣就解放了服務器。
線程池的概念:
首先建立一些線程,他們的集合稱爲線程池,當服務器接收到一個客戶請求後,就從線程池中取出一個空餘的線程爲之服務,服務完後不關閉線程,而是將線程放回到線程池中。
相關類和方法:
Executors是一個工具類和線程池的工廠類,用於建立並返回不一樣類型的線程池,經常使用的方法以下:
返回值 | 方法 | 功能介紹 |
---|---|---|
static ExecutorService | newFixedThreadPool(int nThreads) | 建立一個固定大小的線程池,若是任務數超出線程數,則超出的部分會在隊列中等待 |
static ExecutorService | newCachedThreadPool() | 建立一個可已根據須要建立新線程的線程池,若是同一時間的任務數大於線程數,則能夠根據任務數建立新線程,若是任務執行完成,則緩存一段時間後線程被回收。 |
static ExecutorService | newSingleThreadExecutor() | 建立單個線程數的線程池,能夠保證先進先出的執行順序 |
static ScheduledExecutorService | newScheduledThreadPool(int corePoolSize) | 建立一個能夠執行延遲任務的線程池 |
static ScheduledExecutorService | newSingleThreadScheduledExecutor() | 建立一個單線程的能夠執行延遲任務的線程池 |
static ExecutorService | newWorkStealingPool() | 建立一個搶佔式執行的線程池(任務執行順序不肯定)【JDK 1.8 添加】 |
ThreadPoolExecutor經過構造方法建立線程池,最多能夠設置7個參數,建立線程池的構造方法以下:
構造方法 | 功能介紹 |
---|---|
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) | 經過最原始的方法建立線程池 |
經過上面兩類方法建立完線程池後均可以用ExecutorService接口進行接收,它是真正的線程池接口,主要實現類是ThreadPoolExecutor,經常使用方法以下:
方法聲明 | 功能介紹 |
---|---|
void execute(Runnable command) | 執行任務和命令,一般用於執行Runnable |
<T> Future<T> submit(Callable<T> task) | 執行任務和命令,一般用於執行Callable |
void shutdown() | 啓動有序關閉 |
代碼實例:
使用newFixedThreadPool方法建立線程池
public class FixedThreadPool { public static void main(String[] args) { // 建立含有兩個線程的線程池 ExecutorService threadPool = Executors.newFixedThreadPool(2); // 建立任務 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("線程:" + Thread.currentThread().getName() + "執行了任務!"); } }; // 線程池執行任務 threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); } } 輸出結果: 線程:pool-1-thread-2執行了任務! 線程:pool-1-thread-1執行了任務! 線程:pool-1-thread-2執行了任務! 線程:pool-1-thread-1執行了任務!
從結果上能夠看出,這四個任務分別被線程池中的固定的兩個線程所執行,線程池也不會建立新的線程來執行任務。
使用newCachedThreadPool方法建立線程池
public class cachedThreadPool { public static void main(String[] args) { //1.建立線程池 ExecutorService executorService = Executors.newCachedThreadPool(); //2.設置任務 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("線程:" + Thread.currentThread().getName() + "執行了任務!"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } } }; //3.執行任務 for (int i = 0; i < 100; i ++) { executorService.execute(runnable); } } } 輸出結果: 線程:pool-1-thread-1執行了任務! 線程:pool-1-thread-4執行了任務! 線程:pool-1-thread-3執行了任務! 線程:pool-1-thread-2執行了任務! 線程:pool-1-thread-5執行了任務! 線程:pool-1-thread-7執行了任務! 線程:pool-1-thread-6執行了任務! 線程:pool-1-thread-8執行了任務! 線程:pool-1-thread-9執行了任務! 線程:pool-1-thread-10執行了任務!
從結果上能夠看出,線程池根據任務的數量來建立對應的線程數量。
使用newSingleThreadExecutor的方法建立線程池
public class singleThreadExecutor { public static void main(String[] args) { //建立線程池 ExecutorService executorService = Executors.newSingleThreadExecutor(); //執行任務 for (int i = 0; i < 10; i ++) { final int task = i + 1; executorService.execute(()->{ System.out.println("線程:" + Thread.currentThread().getName() + "執行了第" + task +"任務!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } } } 輸出結果: 線程:pool-1-thread-1執行了第1任務! 線程:pool-1-thread-1執行了第2任務! 線程:pool-1-thread-1執行了第3任務! 線程:pool-1-thread-1執行了第4任務! 線程:pool-1-thread-1執行了第5任務! 線程:pool-1-thread-1執行了第6任務! 線程:pool-1-thread-1執行了第7任務! 線程:pool-1-thread-1執行了第8任務! 線程:pool-1-thread-1執行了第9任務! 線程:pool-1-thread-1執行了第10任務!
從結果能夠看出,該方法建立的線程能夠保證任務執行的順序。
使用newScheduledThreadPool的方法建立線程池
public class ScheduledThreadPool { public static void main(String[] args) { //建立包含2個線程的線程池 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); //記錄建立任務時的當前時間 SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date startTime = new Date(); String start = formatter.format(startTime); System.out.println("建立任務時的時間:" + start); //建立任務 Runnable runnable = new Runnable() { @Override public void run() { Date endTime = new Date(); String end = formatter.format(endTime); System.out.println("線程:" + Thread.currentThread().getName() + "任務執行的時間爲:" + end); } }; //執行任務(參數:runnable-要執行的任務,2-從如今開始延遲執行的時間,TimeUnit.SECONDS-延遲參數的時間單位) for(int i = 0; i < 2; i ++) { scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS); } } } 輸出結果: 建立任務的時間:2021-04-19 19:26:18 線程:pool-1-thread-1任務執行的時間爲:2021-04-19 19:26:20 線程:pool-1-thread-2任務執行的時間爲:2021-04-19 19:26:20
從結果能夠看出,該方法建立的線程池能夠分配已有的線程執行一些須要延遲的任務。
使用newSingleThreadScheduledExecutor方法建立線程池
public class SingleThreadScheduledExecutor { public static void main(String[] args) { //建立線程池 ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); //建立任務 Date startTime = new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String start = formatter.format(startTime); System.out.println("建立任務的時間:" + start); Runnable runnable = new Runnable() { @Override public void run() { Date endTime = new Date(); String end = formatter.format(endTime); System.out.println("線程:" + Thread.currentThread().getName() + "任務執行的時間爲:" + end); } }; //執行任務 for(int i = 0; i < 2; i ++) { scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS); } } } 輸出結果: 建立任務的時間:2021-04-19 19:51:58 線程:pool-1-thread-1任務執行的時間爲:2021-04-19 19:52:00 線程:pool-1-thread-1任務執行的時間爲:2021-04-19 19:52:00
從結果能夠看出,該方法建立的線程池只有一個線程,該線程去執行一些須要延遲的任務。
使用newWorkStealingPool方法建立線程池
public class newWorkStealingPool { public static void main(String[] args) { //建立線程池 ExecutorService executorService = Executors.newWorkStealingPool(); //執行任務 for (int i = 0; i < 4; i ++) { final int task = i + 1; executorService.execute(()->{ System.out.println("線程:" + Thread.currentThread().getName() + "執行了第" + task +"任務!"); }); } //確保任務被執行 while (!executorService.isTerminated()) { } } } 輸出結果: 線程:ForkJoinPool-1-worker-9執行了第1任務! 線程:ForkJoinPool-1-worker-4執行了第4任務! 線程:ForkJoinPool-1-worker-11執行了第3任務! 線程:ForkJoinPool-1-worker-2執行了第2任務!
從結果能夠看出,該方法會建立一個含有足夠多線程的線程池,來維持相應的並行級別,任務會被搶佔式執行。(任務執行順序不肯定)
使用ThreadPoolExecutor建立線程池
在編寫示例代碼以前我先來說一個生活的例子(去銀行辦理業務):
描述業務場景:銀行一共有4個窗口,今天只開放兩個,而後等候區一共3個位置。以下圖所示:
若是銀行同時辦理業務的人小於等於5我的,那麼正好,2我的先辦理,其餘的人在等候區等待。以下圖所示:
若是銀行同時辦理業務的人等於6我的時,銀行會開放三號窗口來辦理業務。以下圖所示:
若是銀行同時辦理業務的人等於7我的時,銀行會開放四號窗口來辦理業務。以下圖所示:
若是銀行同時辦理業務的人大於7我的時,則銀行大廳經理就會告訴後面的人,該網點業務已滿,請去其餘網點辦理。
如今咱們再來看一下咱們的ThreadPoolExecutor構造方法,該構造方法最多能夠設置7個參數:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
參數介紹:
unit:時間單位,是第三個參數的單位,這兩個參數組合成最大線程數的存活時間
workQueue:等待隊列,用於保存在線程池等待的任務(對應銀行辦理業務模型:等待區)
handler:拒絕策略,任務超出線程池可接受範圍時,拒絕處理任務時的策略。
當任務數小於等於核心線程數+等待隊列數量的總和時:
public class ThreadPoolExecutorTest { public static void main(String[] args) { // 建立線程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); //建立任務 Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "==>執行任務"); } }; // 執行任務 for (int i = 0; i < 5; i++) { threadPool.execute(runnable); } //關閉線程池 threadPool.shutdown(); } } 輸出結果: pool-1-thread-2==>執行任務 pool-1-thread-1==>執行任務 pool-1-thread-2==>執行任務 pool-1-thread-1==>執行任務 pool-1-thread-2==>執行任務
從結果中能夠看出,只有兩個核心線程在執行任務。
當任務數大於核心線程數+等待隊列數量的總和,可是小於等於最大線程數時:
public class ThreadPoolExecutorTest { public static void main(String[] args) { // 建立線程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); //建立任務 Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "==>執行任務"); } }; // 執行任務 for (int i = 0; i < 7; i++) { threadPool.execute(runnable); } //關閉線程池 threadPool.shutdown(); } } 輸出結果: pool-1-thread-1==>執行任務 pool-1-thread-4==>執行任務 pool-1-thread-2==>執行任務 pool-1-thread-2==>執行任務 pool-1-thread-3==>執行任務 pool-1-thread-4==>執行任務 pool-1-thread-1==>執行任務
從結果中能夠看出,啓動了最大線程來執行任務。
當任務數大於最大線程數時:
public class ThreadPoolExecutorTest { public static void main(String[] args) { // 建立線程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); //建立任務 Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "==>執行任務"); } }; // 執行任務 for (int i = 0; i < 8; i++) { threadPool.execute(runnable); } //關閉線程池 threadPool.shutdown(); } } 輸出結果: Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.zck.task18.ThreadPool.ThreadPoolExecutorTest$1@7f31245a rejected from java.util.concurrent.ThreadPoolExecutor@6d6f6e28[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 7] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) at com.zck.task18.ThreadPool.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:25) pool-1-thread-1==>執行任務 pool-1-thread-4==>執行任務 pool-1-thread-4==>執行任務 pool-1-thread-4==>執行任務 pool-1-thread-2==>執行任務 pool-1-thread-3==>執行任務 pool-1-thread-1==>執行任務
從結果中能夠看出,任務大於最大線程數,使用拒絕策略直接拋出異常。
本文介紹了三種線程的建立方式:
介紹了七種線程池的建立方式: