前言
聲明:該文章中所有測試都是在JDK1.8的環(huán)境下等太。
該文章是我在學(xué)習(xí)Java中的多線程這方面知識時架专,做的一些總結(jié)和記錄。
如果有不正確的地方請大家多多包涵并作出指點殖氏,謝謝蛉谜!
本文章有些內(nèi)容參考并采用以下作品內(nèi)容:
https://www.cnblogs.com/vipstone/p/14149065.html
https://www.bilibili.com/video/BV1dt4y1i7Gt?t=1056
一、基礎(chǔ)概念
我們知道CPU執(zhí)行代碼都是一條一條順序執(zhí)行的崇堵,所以本質(zhì)上單核CPU的電腦不會在同一個時間點執(zhí)行多個任務(wù)型诚。但是在現(xiàn)實中,我們在使用電腦的時候鸳劳,可以一邊聊微信狰贯,一邊聽歌。那這是怎么做到的呢赏廓?其實操作系統(tǒng)多任務(wù)就是讓CPU對多個任務(wù)輪流交替執(zhí)行涵紊。
舉個例子:在一個教室中同時坐著一年級,二年級捐晶,三年級三批學(xué)生县钥,老師花一分鐘教一年級卸察,花一分教二年級,花一分鐘教三年級驱负,這樣輪流下去,看上去就像同時在教三個年級患雇。
同樣的跃脊,我們使用電腦時,一邊聊微信苛吱,一邊聽歌也是這個原理酪术。CPU讓微信執(zhí)行0.001秒,讓音樂播放器執(zhí)行0.001秒翠储,在我們看來绘雁,CPU就是在同時執(zhí)行多個任務(wù)。
1.1 程序彰亥、進程和線程的概念
程序:被存儲在磁盤或其他的數(shù)據(jù)存儲設(shè)備中的可執(zhí)行文件咧七,也就是一堆靜態(tài)的代碼。
進程:運行在內(nèi)存中可執(zhí)行程序?qū)嵗?/p>
線程:線程是進程的一個實體任斋,是CPU調(diào)度和分派的基本單位继阻。
看著這些概念是不是很抽象耻涛,看的很不舒服,那么下面我來用實例解釋一下以上幾個概念瘟檩。
1.2 程序的運行實例
上面說到抹缕,我們使用電腦時,可以一邊聊微信墨辛,一邊聽歌卓研。那這些軟件運行的整個過程是怎樣的呢?
- 如果我們要用微信聊天睹簇,大部分的人都是雙擊擊桌面上的"微信"快捷方式奏赘,而雙擊這個快捷方式打開的實際上是一個.exe文件,這個.exe文件就是一個程序太惠,請看下圖:
- 雙擊.exe文件后磨淌,加載可執(zhí)行程序到內(nèi)存中,方便CPU讀取凿渊,那這個可執(zhí)行文件程序?qū)嵗褪且粋€進程梁只。請看下圖:
- 而我們在使用微信的時候,微信會做很多事情埃脏,比如加載微信UI界面搪锣,顯示微信好友列表,與好友聊天彩掐,這些可以看成微信進程中一個個單獨的線程构舟。
我用一張圖來總結(jié)一下整個過程:
根據(jù)上面內(nèi)容對于線程概念的了解,是否有個疑問佩谷,線程是怎么創(chuàng)建出來的旁壮?帶著這個疑問我們就來學(xué)習(xí)一下java中的線程是怎么如何創(chuàng)建的。
二谐檀、線程的創(chuàng)建
2.1 Thread類的概念
java.lang.Thread類代表線程抡谐,任何線程都是Thread類(子類)的實例。
2.2 常用的方法
構(gòu)造方法 | 功能介紹 |
---|---|
Thread() | 使用無參的方式構(gòu)造對象 |
Thread(String name) | 根據(jù)參數(shù)指定的名稱來構(gòu)造對象 |
Thread(Runnable target) | 根據(jù)參數(shù)指定的引用來構(gòu)造對象桐猬,其中Runnable是個接口類型 |
Thread(Runnable target, String name) | 根據(jù)參數(shù)指定引用和名稱來構(gòu)造對象 |
成員方法 | 功能介紹 |
---|---|
run() | 1.使用Runnable引用構(gòu)造線程對象麦撵,調(diào)用該方法時最終調(diào)用接口中的版本<br />2.沒有使用Runnable引用構(gòu)造線程對象,調(diào)用該方法時則啥也不做 |
start() | 用于啟動線程溃肪,Java虛擬機會自動調(diào)用該線程的run方法 |
2.3 創(chuàng)建方式
2.3.1 自定義Thread類創(chuàng)建
自定義類繼承Thread類并根據(jù)自己的需求重寫run方法免胃,然后在主類中創(chuàng)建該類的對象調(diào)用start方法,這樣就啟動了一個線程惫撰。
示例代碼如下:
//創(chuàng)建一個自定義類SubThreadRun繼承Thread類羔沙,作為一個可以備用的線程類
public class SubThreadRun extends Thread {
@Override
public void run() {
//打印1~20的整數(shù)值
for (int i = 0; i < 20 ; i ++) {
System.out.println("SubThreadRun線程中:" + i);
}
}
}
//在主方法中創(chuàng)建該線程并啟動
public class SubThreadRunTest {
public static void main(String[] args) {
//1.申明Thread類型的引用指向子類類型的對象
Thread t1 = new SubThreadRun();
//用于啟動線程,java虛擬機會自動調(diào)用該線程中的run方法
t1.start();
}
}
輸出結(jié)果:
SubThreadRun線程中:0
SubThreadRun線程中:2
厨钻。扼雏。坚嗜。。诗充。苍蔬。
SubThreadRun線程中:19
到這里大家會不會有以下一個疑問,看示例代碼:
public class SubThreadRunTest {
public static void main(String[] args) {
//1.申明Thread類型的引用指向子類類型的對象
Thread t1 = new SubThreadRun();
//調(diào)用run方法測試
t1.run();
}
}
輸出結(jié)果:
SubThreadRun線程中:0
SubThreadRun線程中:1
蝴蜓。碟绑。。茎匠。格仲。。
SubThreadRun線程中:19
我們不調(diào)用start方法诵冒,而是直接調(diào)用run方法抓狭,發(fā)現(xiàn)結(jié)果和調(diào)用start方法一樣,他們兩個方法的區(qū)別是啥呢造烁?
我們在主方法中也加入一個打印1-20的數(shù),然后分別用run方法和start方法進行測試午笛,實例代碼如下:
//使用run方法進行測試
public class SubThreadRunTest {
public static void main(String[] args) {
//1.申明Thread類型的引用指向子類類型的對象
Thread t1 = new SubThreadRun();
//2.調(diào)用run方法測試
t1.run();
//打印1~20的整數(shù)值
for (int i = 0; i < 20 ; i ++) {
System.out.println("-----mian-----方法中:" + i);
}
}
}
輸出結(jié)果:
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虛擬機會自動調(diào)用該線程中的run方法
t1.start();
//打印1~20的整數(shù)值
for (int i = 0; i < 20 ; i ++) {
System.out.println("-----mian-----方法中:" + i);
}
}
}
輸出結(jié)果:
SubThreadRun線程中:0
-----mian-----方法中:0
SubThreadRun線程中:1
SubThreadRun線程中:2
-----mian-----方法中:1
。掸冤。厘托。。稿湿。铅匹。//SubThreadRun線程和mian方法相互穿插
SubThreadRun線程中:19
-----mian-----方法中:19
從上面的例子可知:
- 調(diào)用run方法測試時,本質(zhì)上就是相當(dāng)于對普通成員方法的調(diào)用饺藤,因此執(zhí)行流程就是run方法的代碼執(zhí)行完畢后才能繼續(xù)向下執(zhí)行包斑。
- 調(diào)用start方法測試時流礁,相當(dāng)于又啟動了一個線程,加上執(zhí)行main方法的線程舰始,一共有兩個線程崇棠,這兩個線程同時運行,所以輸出結(jié)果是交錯的丸卷。(現(xiàn)在回過頭來想想枕稀,現(xiàn)在是不是有點理解我可以用qq音樂一邊聽歌,一邊在打字評論了呢谜嫉。)
第一種創(chuàng)建線程的方式我們已經(jīng)學(xué)會了萎坷,那這種創(chuàng)建線程的方式有沒有什么缺陷呢?假如SubThreadRun類已經(jīng)繼承了一個父類沐兰,這個時候我們又要把該類作為自定義線程類哆档,如果還是用繼承Thread類的方法來實現(xiàn)的話就違背了Java不可多繼承的概念。所以第二種創(chuàng)建方式就可以避免這種問題住闯。
2.3.2 通過實現(xiàn)Runnable接口實現(xiàn)創(chuàng)建
自定義類實現(xiàn)Runnable接口并重寫run方法瓜浸,創(chuàng)建該類的對象作為實參來構(gòu)造Thread類型的對象,然后使用Thread類型的對象調(diào)用start方法比原。
示例代碼如下:
//創(chuàng)建一個自定義類SubRunnableRun實現(xiàn)Runnable接口
public class SubRunnableRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20 ; i ++) {
System.out.println("SubRunnableRun線程中:" + i);
}
}
}
//在主方法中創(chuàng)建該線程并啟動
public class SunRunnableRunTest {
public static void main(String[] args) {
//1.創(chuàng)建自定義類型的對象
SubRunnableRun srr = new SubRunnableRun();
//2.使用該對象作為實參構(gòu)造Thread類型的對象
Thread t1 = new Thread(srr);
//3.使用Thread類型的對象調(diào)用start方法
t1.start();
//打印1~20之間的所有整數(shù)
for (int i = 0; i < 20 ; i ++) {
System.out.println("-----mian-----方法中:" + i);
}
}
}
輸出結(jié)果:
SubRunnableRun線程中:0
-----mian-----方法中:0
SubRunnableRun線程中:1
SubRunnableRun線程中:2
-----mian-----方法中:1
插佛。。量窘。雇寇。。蚌铜。//SubRunnableRun線程和mian方法相互穿插
SubRunnableRun線程中:19
-----mian-----方法中:19
到這里大家會不會有一個疑問呢锨侯?
我在SunRunnableRunTest類的main方法中也實例化了Thread類,為什么該線程調(diào)用的是實現(xiàn)了Runnable接口的SubRunnableRun類中的run方法冬殃,而不是Thread類中的run方法囚痴。
為了解決該疑問,我們就進入Thread類去看一下源碼造壮,源碼調(diào)用過程如下:
-
從上面的SunRunnableRunTest類中代碼可知渡讼,我們創(chuàng)建線程調(diào)用的是Thread的有參構(gòu)造方法,參數(shù)是Runnable類型的耳璧。
進入到Thread類中找到該有參構(gòu)造方法成箫,看到該構(gòu)造方法調(diào)用init方法,并且把target參數(shù)繼續(xù)當(dāng)參數(shù)傳遞過去旨枯。
-
轉(zhuǎn)到對應(yīng)的init方法后蹬昌,發(fā)現(xiàn)該init方法繼續(xù)調(diào)用另一個重載的init方法,并且把target參數(shù)繼續(xù)當(dāng)參數(shù)傳遞過去攀隔。
-
繼續(xù)進入到重載的init方法中皂贩,我們發(fā)現(xiàn)栖榨,該方法中把參數(shù)中target賦值給成員變量target。
-
然后找到Thread類中的run方法明刷,發(fā)現(xiàn)只要Thread的成員變量target存在婴栽,就調(diào)用target中的run方法。
通過查看源碼辈末,我們可以知道為什么我們創(chuàng)建的Thread類調(diào)用的是Runnable類中的run方法愚争。
2.3.3 匿名內(nèi)部類的方式實現(xiàn)創(chuàng)建
上面兩種創(chuàng)建線程的方式都需要單獨創(chuàng)建一個類來繼承Thread類或者實現(xiàn)Runnable接口,并重寫run方法挤聘。而匿名內(nèi)部類可以不創(chuàng)建單獨的類而實現(xiàn)自定義線程的創(chuàng)建轰枝。
示例代碼如下:
public class ThreadNoNameTest {
public static void main(String[] args) {
//匿名內(nèi)部類的語法格式:父類/接口類型 引用變量 = new 父類/接口類型 {方法的重寫};
//1.使用繼承加匿名內(nèi)部類的方式創(chuàng)建并啟動線程
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("繼承Thread類方式創(chuàng)建線程...");
}
};
t1.start();
//2.使用接口加匿名內(nèi)部類的方式創(chuàng)建并啟動線程
Runnable ra = new Runnable() {
@Override
public void run() {
System.out.println("實現(xiàn)Runnable接口方式實現(xiàn)線程...");
}
};
Thread t2 = new Thread(ra);
t2.start();
}
}
這兩個利用匿名內(nèi)部類創(chuàng)建線程的方式還能繼續(xù)簡化代碼,尤其是使用Runnable接口創(chuàng)建線程的方式组去,可以使用Lambda表達式進行簡化鞍陨。
示例代碼如下:
public class ThreadNoNameTest {
public static void main(String[] args) {
//1.使用繼承加匿名內(nèi)部類簡化后的方式創(chuàng)建并啟動線程
new Thread() {
@Override
public void run() {
System.out.println("簡化后繼承Thread類方式創(chuàng)建線程...");
}
}.start();
//2.使用接口加匿名內(nèi)部類簡化后的方式創(chuàng)建并啟動線程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("簡化后實現(xiàn)Runnable接口方式實現(xiàn)線程...");
}
}).start();
//2-1.對于接口加匿名內(nèi)部創(chuàng)建線程,可以繼續(xù)使用lambda表達式進行簡化从隆。
//Java8開始支持lambda表達式:(形參列表) -> {方法體;}
Runnable ra = () -> System.out.println("lambda表達式簡化實現(xiàn)Runnable接口方式實現(xiàn)線程...");
new Thread(ra).start();
//繼續(xù)簡化
new Thread(() -> System.out.println("lambda表達式簡化實現(xiàn)Runnable接口方式實現(xiàn)線程...")).start();
}
}
2.3.4 通過實現(xiàn)Callable接口創(chuàng)建
通過上面幾個例子诚撵,我們了解了兩種創(chuàng)建線程的方式,但是這兩種方式創(chuàng)建線程存在一個問題键闺,就是run方法是沒有返回值的砾脑,所以如果我們希望在線程結(jié)束之后給出一個結(jié)果,那就需要用到實現(xiàn)Callable接口創(chuàng)建線程艾杏。
(1)Callable接口
從Java5開始新增創(chuàng)建線程的第三中方式為實現(xiàn)java.util.concurrent.Callable接口。
常用方法如下:
成員方法 | 功能介紹 |
---|---|
V call() | 計算結(jié)果盅藻,如果無法計算結(jié)果购桑,則拋出一個異常 |
我們知道啟動線程只有創(chuàng)建一個Thread類并調(diào)用start方法,如果想讓線程啟動時調(diào)用到Callable接口中的call方法氏淑,就得用到FutureTask類勃蜘。
(2)FutureTask類
java.util.concurrent.FutureTask類實現(xiàn)了RunnableFuture接口,RunnableFuture接口是Runnable和Future的綜合體假残,作為一個Future缭贡,F(xiàn)utureTask可以執(zhí)行異步計算,可以查看異步程序是否執(zhí)行完畢辉懒,并且可以開始和取消程序阳惹,并取得程序最終的執(zhí)行結(jié)果,也可以用于獲取調(diào)用方法后的返回結(jié)果眶俩。
常用方法如下:
構(gòu)造方法 | 功能介紹 |
---|---|
FutureTask(Callable<v> callable) | 創(chuàng)建一個FutureTask莹汤,它將在運行時執(zhí)行給定的Callable |
成員方法 | 功能介紹 |
---|---|
V get() | 獲取call方法計算的結(jié)果 |
從上面的概念可以了解到FutureTask類的一個構(gòu)造方法是以Callable為參數(shù)的,然后FutureTask類是Runnable的子類颠印,所以FutureTask類可以作為Thread類的參數(shù)纲岭。這樣的話我們就可以創(chuàng)建一個線程并調(diào)用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方法中的結(jié)果:" + 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方法中的結(jié)果:" + ob);
}
}
輸出結(jié)果:
call方法中的結(jié)果:50005000
main方法中的結(jié)果:50005000
2.3.5 線程池的創(chuàng)建
線程池的由來:
在講線程池之前,先來講一個故事止潮,一個老板開個飯店窃判,但這個飯店很奇怪,每來一個顧客喇闸,老板就去招一個新的大廚來做菜袄琳,等這個顧客走后,老板直接把這個大廚辭了仅偎。如果是按這種經(jīng)營方式的話跨蟹,老板每天就忙著招大廚,啥事都干不了橘沥。
對于上面講的這個故事窗轩,我們現(xiàn)實生活中的飯店老板可沒有這么蠢,他們都是在開店前就直接招了好幾個大廚候在廚房座咆,等有顧客來了痢艺,直接做菜上菜,顧客走后介陶,廚師留在后廚待命堤舒,這樣就把老板解放了。
現(xiàn)在我們來講一下線程池的由來:比如說服務(wù)器編程中哺呜,如果為每一個客戶都分配一個新的工作線程舌缤,并且當(dāng)工作線程與客戶通信結(jié)束時,這個線程被銷毀某残,這就需要頻繁的創(chuàng)建和銷毀工作線程国撵。如果訪問服務(wù)器的客戶端過多,那么會嚴(yán)重影響服務(wù)器的性能玻墅。
那么我們該如何解放服務(wù)器呢介牙?對了,就像上面講的飯店老板一樣澳厢,打造一個后廚环础,讓廚師候著。相對于服務(wù)器來說剩拢,就創(chuàng)建一個線程池线得,讓線程候著,等待客戶端的連接徐伐,等客戶端結(jié)束通信后框都,服務(wù)器不關(guān)閉該線程,而是返回到線程中待命。這樣就解放了服務(wù)器魏保。
線程池的概念:
首先創(chuàng)建一些線程熬尺,他們的集合稱為線程池,當(dāng)服務(wù)器接收到一個客戶請求后谓罗,就從線程池中取出一個空余的線程為之服務(wù)粱哼,服務(wù)完后不關(guān)閉線程,而是將線程放回到線程池中檩咱。
相關(guān)類和方法:
- 線程池的創(chuàng)建方法總共有 7 種揭措,但總體來說可分為 2 類:
-
Executors是一個工具類和線程池的工廠類,用于創(chuàng)建并返回不同類型的線程池刻蚯,常用的方法如下:
返回值 方法 功能介紹 static ExecutorService newFixedThreadPool(int nThreads) 創(chuàng)建一個固定大小的線程池绊含,如果任務(wù)數(shù)超出線程數(shù),則超出的部分會在隊列中等待 static ExecutorService newCachedThreadPool() 創(chuàng)建一個可已根據(jù)需要創(chuàng)建新線程的線程池炊汹,如果同一時間的任務(wù)數(shù)大于線程數(shù)躬充,則可以根據(jù)任務(wù)數(shù)創(chuàng)建新線程,如果任務(wù)執(zhí)行完成讨便,則緩存一段時間后線程被回收充甚。 static ExecutorService newSingleThreadExecutor() 創(chuàng)建單個線程數(shù)的線程池,可以保證先進先出的執(zhí)行順序 static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 創(chuàng)建一個可以執(zhí)行延遲任務(wù)的線程池 static ScheduledExecutorService newSingleThreadScheduledExecutor() 創(chuàng)建一個單線程的可以執(zhí)行延遲任務(wù)的線程池 static ExecutorService newWorkStealingPool() 創(chuàng)建一個搶占式執(zhí)行的線程池(任務(wù)執(zhí)行順序不確定)【JDK 1.8 添加】 -
ThreadPoolExecutor通過構(gòu)造方法創(chuàng)建線程池霸褒,最多可以設(shè)置7個參數(shù)伴找,創(chuàng)建線程池的構(gòu)造方法如下:
構(gòu)造方法 功能介紹 ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 通過最原始的方法創(chuàng)建線程池 -
通過上面兩類方法創(chuàng)建完線程池后都可以用ExecutorService接口進行接收,它是真正的線程池接口废菱,主要實現(xiàn)類是ThreadPoolExecutor技矮,常用方法如下:
方法聲明 功能介紹 void execute(Runnable command) 執(zhí)行任務(wù)和命令,通常用于執(zhí)行Runnable <T> Future<T> submit(Callable<T> task) 執(zhí)行任務(wù)和命令殊轴,通常用于執(zhí)行Callable void shutdown() 啟動有序關(guān)閉
代碼實例:
-
使用newFixedThreadPool方法創(chuàng)建線程池
public class FixedThreadPool { public static void main(String[] args) { // 創(chuàng)建含有兩個線程的線程池 ExecutorService threadPool = Executors.newFixedThreadPool(2); // 創(chuàng)建任務(wù) Runnable runnable = new Runnable() { @Override public void run() { System.out.println("線程:" + Thread.currentThread().getName() + "執(zhí)行了任務(wù)穆役!"); } }; // 線程池執(zhí)行任務(wù) threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); } } 輸出結(jié)果: 線程:pool-1-thread-2執(zhí)行了任務(wù)! 線程:pool-1-thread-1執(zhí)行了任務(wù)梳凛! 線程:pool-1-thread-2執(zhí)行了任務(wù)! 線程:pool-1-thread-1執(zhí)行了任務(wù)梳杏!
從結(jié)果上可以看出韧拒,這四個任務(wù)分別被線程池中的固定的兩個線程所執(zhí)行,線程池也不會創(chuàng)建新的線程來執(zhí)行任務(wù)十性。
-
使用newCachedThreadPool方法創(chuàng)建線程池
public class cachedThreadPool { public static void main(String[] args) { //1.創(chuàng)建線程池 ExecutorService executorService = Executors.newCachedThreadPool(); //2.設(shè)置任務(wù) Runnable runnable = new Runnable() { @Override public void run() { System.out.println("線程:" + Thread.currentThread().getName() + "執(zhí)行了任務(wù)叛溢!"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } } }; //3.執(zhí)行任務(wù) for (int i = 0; i < 100; i ++) { executorService.execute(runnable); } } } 輸出結(jié)果: 線程:pool-1-thread-1執(zhí)行了任務(wù)! 線程:pool-1-thread-4執(zhí)行了任務(wù)劲适! 線程:pool-1-thread-3執(zhí)行了任務(wù)楷掉! 線程:pool-1-thread-2執(zhí)行了任務(wù)! 線程:pool-1-thread-5執(zhí)行了任務(wù)霞势! 線程:pool-1-thread-7執(zhí)行了任務(wù)烹植! 線程:pool-1-thread-6執(zhí)行了任務(wù)斑鸦! 線程:pool-1-thread-8執(zhí)行了任務(wù)! 線程:pool-1-thread-9執(zhí)行了任務(wù)草雕! 線程:pool-1-thread-10執(zhí)行了任務(wù)巷屿!
從結(jié)果上可以看出,線程池根據(jù)任務(wù)的數(shù)量來創(chuàng)建對應(yīng)的線程數(shù)量墩虹。
-
使用newSingleThreadExecutor的方法創(chuàng)建線程池
public class singleThreadExecutor { public static void main(String[] args) { //創(chuàng)建線程池 ExecutorService executorService = Executors.newSingleThreadExecutor(); //執(zhí)行任務(wù) for (int i = 0; i < 10; i ++) { final int task = i + 1; executorService.execute(()->{ System.out.println("線程:" + Thread.currentThread().getName() + "執(zhí)行了第" + task +"任務(wù)嘱巾!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } } } 輸出結(jié)果: 線程:pool-1-thread-1執(zhí)行了第1任務(wù)! 線程:pool-1-thread-1執(zhí)行了第2任務(wù)诫钓! 線程:pool-1-thread-1執(zhí)行了第3任務(wù)旬昭! 線程:pool-1-thread-1執(zhí)行了第4任務(wù)! 線程:pool-1-thread-1執(zhí)行了第5任務(wù)菌湃! 線程:pool-1-thread-1執(zhí)行了第6任務(wù)问拘! 線程:pool-1-thread-1執(zhí)行了第7任務(wù)! 線程:pool-1-thread-1執(zhí)行了第8任務(wù)慢味! 線程:pool-1-thread-1執(zhí)行了第9任務(wù)场梆! 線程:pool-1-thread-1執(zhí)行了第10任務(wù)!
從結(jié)果可以看出纯路,該方法創(chuàng)建的線程可以保證任務(wù)執(zhí)行的順序或油。
-
使用newScheduledThreadPool的方法創(chuàng)建線程池
public class ScheduledThreadPool { public static void main(String[] args) { //創(chuàng)建包含2個線程的線程池 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); //記錄創(chuàng)建任務(wù)時的當(dāng)前時間 SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date startTime = new Date(); String start = formatter.format(startTime); System.out.println("創(chuàng)建任務(wù)時的時間:" + start); //創(chuàng)建任務(wù) Runnable runnable = new Runnable() { @Override public void run() { Date endTime = new Date(); String end = formatter.format(endTime); System.out.println("線程:" + Thread.currentThread().getName() + "任務(wù)執(zhí)行的時間為:" + end); } }; //執(zhí)行任務(wù)(參數(shù):runnable-要執(zhí)行的任務(wù),2-從現(xiàn)在開始延遲執(zhí)行的時間驰唬,TimeUnit.SECONDS-延遲參數(shù)的時間單位) for(int i = 0; i < 2; i ++) { scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS); } } } 輸出結(jié)果: 創(chuàng)建任務(wù)的時間:2021-04-19 19:26:18 線程:pool-1-thread-1任務(wù)執(zhí)行的時間為:2021-04-19 19:26:20 線程:pool-1-thread-2任務(wù)執(zhí)行的時間為:2021-04-19 19:26:20
從結(jié)果可以看出顶岸,該方法創(chuàng)建的線程池可以分配已有的線程執(zhí)行一些需要延遲的任務(wù)。
-
使用newSingleThreadScheduledExecutor方法創(chuàng)建線程池
public class SingleThreadScheduledExecutor { public static void main(String[] args) { //創(chuàng)建線程池 ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); //創(chuàng)建任務(wù) Date startTime = new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String start = formatter.format(startTime); System.out.println("創(chuàng)建任務(wù)的時間:" + start); Runnable runnable = new Runnable() { @Override public void run() { Date endTime = new Date(); String end = formatter.format(endTime); System.out.println("線程:" + Thread.currentThread().getName() + "任務(wù)執(zhí)行的時間為:" + end); } }; //執(zhí)行任務(wù) for(int i = 0; i < 2; i ++) { scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS); } } } 輸出結(jié)果: 創(chuàng)建任務(wù)的時間:2021-04-19 19:51:58 線程:pool-1-thread-1任務(wù)執(zhí)行的時間為:2021-04-19 19:52:00 線程:pool-1-thread-1任務(wù)執(zhí)行的時間為:2021-04-19 19:52:00
從結(jié)果可以看出叫编,該方法創(chuàng)建的線程池只有一個線程辖佣,該線程去執(zhí)行一些需要延遲的任務(wù)。
-
使用newWorkStealingPool方法創(chuàng)建線程池
public class newWorkStealingPool { public static void main(String[] args) { //創(chuàng)建線程池 ExecutorService executorService = Executors.newWorkStealingPool(); //執(zhí)行任務(wù) for (int i = 0; i < 4; i ++) { final int task = i + 1; executorService.execute(()->{ System.out.println("線程:" + Thread.currentThread().getName() + "執(zhí)行了第" + task +"任務(wù)搓逾!"); }); } //確保任務(wù)被執(zhí)行 while (!executorService.isTerminated()) { } } } 輸出結(jié)果: 線程:ForkJoinPool-1-worker-9執(zhí)行了第1任務(wù)卷谈! 線程:ForkJoinPool-1-worker-4執(zhí)行了第4任務(wù)! 線程:ForkJoinPool-1-worker-11執(zhí)行了第3任務(wù)霞篡! 線程:ForkJoinPool-1-worker-2執(zhí)行了第2任務(wù)世蔗!
從結(jié)果可以看出,該方法會創(chuàng)建一個含有足夠多線程的線程池朗兵,來維持相應(yīng)的并行級別污淋,任務(wù)會被搶占式執(zhí)行。(任務(wù)執(zhí)行順序不確定)
-
使用ThreadPoolExecutor創(chuàng)建線程池
在編寫示例代碼之前我先來講一個生活的例子(去銀行辦理業(yè)務(wù)):
描述業(yè)務(wù)場景:銀行一共有4個窗口余掖,今天只開放兩個寸爆,然后等候區(qū)一共3個位置。如下圖所示:
-
如果銀行同時辦理業(yè)務(wù)的人小于等于5個人,那么正好赁豆,2個人先辦理仅醇,其他的人在等候區(qū)等待。如下圖所示:
如果銀行同時辦理業(yè)務(wù)的人等于6個人時歌憨,銀行會開放三號窗口來辦理業(yè)務(wù)着憨。如下圖所示:
- 如果銀行同時辦理業(yè)務(wù)的人等于7個人時,銀行會開放四號窗口來辦理業(yè)務(wù)务嫡。如下圖所示:
-
如果銀行同時辦理業(yè)務(wù)的人大于7個人時甲抖,則銀行大廳經(jīng)理就會告訴后面的人,該網(wǎng)點業(yè)務(wù)已滿心铃,請去其他網(wǎng)點辦理准谚。
現(xiàn)在我們再來看一下我們的ThreadPoolExecutor構(gòu)造方法,該構(gòu)造方法最多可以設(shè)置7個參數(shù):
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
參數(shù)介紹:
- corePoolSize:核心線程數(shù)去扣,在線程池中一直存在的線程(對應(yīng)銀行辦理業(yè)務(wù)模型:一開始就開放的窗口)
- maximumPoolSize:最大線程數(shù)柱衔,線程池中能創(chuàng)建最多的線程數(shù),除了核心線程數(shù)以外的幾個線程會在線程池的任務(wù)隊列滿了之后創(chuàng)建(對應(yīng)銀行辦理業(yè)務(wù)模型:所有窗口)
- keepAliveTime:最大線程數(shù)的存活時間愉棱,當(dāng)長時間沒有任務(wù)時唆铐,線程池會銷毀一部分線程,保留核心線程
-
unit:時間單位奔滑,是第三個參數(shù)的單位艾岂,這兩個參數(shù)組合成最大線程數(shù)的存活時間
- TimeUnit.DAYS:天
- TimeUnit.HOURS:小時
- TimeUnit.MINUTES:分
- TimeUnit.SECONDS:秒
- TimeUnit.MILLISECONDS:毫秒
- TimeUnit.MICROSECONDS:微秒
- TimeUnit.NANOSECONDS:納秒
-
workQueue:等待隊列,用于保存在線程池等待的任務(wù)(對應(yīng)銀行辦理業(yè)務(wù)模型:等待區(qū))
- ArrayBlockingQueue:一個由數(shù)組支持的有界阻塞隊列朋其。
- LinkedBlockingQueue:一個由鏈表組成的有界阻塞隊列王浴。
- SynchronousQueue:該阻塞隊列不儲存任務(wù),直接提交給線程梅猿,這樣就會形成對于提交的任務(wù)氓辣,如果有空閑線程,則使用空閑線程來處理袱蚓,否則新建一個線程來處理任務(wù)钞啸。
- PriorityBlockingQueue:一個帶優(yōu)先級的無界阻塞隊列,每次出隊都返回優(yōu)先級最高或者最低的元素
- DelayQueue:一個使用優(yōu)先級隊列實現(xiàn)支持延時獲取元素的無界阻塞隊列喇潘,只有在延遲期滿時才能從中提取元素体斩,現(xiàn)實中的使用: 淘寶訂單業(yè)務(wù):下單之后如果三十分鐘之內(nèi)沒有付款就自動取消訂單。
- LinkedTransferQueue:一個由鏈表結(jié)構(gòu)組成的無界阻塞隊列响蓉。
- LinkedBlockingDeque:一個由鏈表結(jié)構(gòu)組成的雙向阻塞隊列。
- threadFactory:線程工廠哨毁,用于創(chuàng)建線程枫甲。
-
handler:拒絕策略,任務(wù)超出線程池可接受范圍時,拒絕處理任務(wù)時的策略想幻。
- ThreadPoolExecutor.AbortPolicy:當(dāng)任務(wù)添加到線程池中被拒絕時粱栖,它將拋出 RejectedExecutionException 異常(默認使用該策略)
- ThreadPoolExecutor.CallerRunsPolicy:當(dāng)任務(wù)添加到線程池中被拒絕時,會調(diào)用當(dāng)前線程池的所在的線程去執(zhí)行被拒絕的任務(wù)
- ThreadPoolExecutor.DiscardOldestPolicy:當(dāng)任務(wù)添加到線程池中被拒絕時脏毯,會拋棄任務(wù)隊列中最舊的任務(wù)也就是最先加入隊列的闹究,再把這個新任務(wù)添加進去
- ThreadPoolExecutor.DiscardPolicy:如果該任務(wù)被拒絕,這直接忽略或者拋棄該任務(wù)
當(dāng)任務(wù)數(shù)小于等于核心線程數(shù)+等待隊列數(shù)量的總和時:
public class ThreadPoolExecutorTest { public static void main(String[] args) { // 創(chuàng)建線程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); //創(chuàng)建任務(wù) Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "==>執(zhí)行任務(wù)"); } }; // 執(zhí)行任務(wù) for (int i = 0; i < 5; i++) { threadPool.execute(runnable); } //關(guān)閉線程池 threadPool.shutdown(); } } 輸出結(jié)果: pool-1-thread-2==>執(zhí)行任務(wù) pool-1-thread-1==>執(zhí)行任務(wù) pool-1-thread-2==>執(zhí)行任務(wù) pool-1-thread-1==>執(zhí)行任務(wù) pool-1-thread-2==>執(zhí)行任務(wù)
從結(jié)果中可以看出食店,只有兩個核心線程在執(zhí)行任務(wù)渣淤。
當(dāng)任務(wù)數(shù)大于核心線程數(shù)+等待隊列數(shù)量的總和,但是小于等于最大線程數(shù)時:
public class ThreadPoolExecutorTest { public static void main(String[] args) { // 創(chuàng)建線程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); //創(chuàng)建任務(wù) Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "==>執(zhí)行任務(wù)"); } }; // 執(zhí)行任務(wù) for (int i = 0; i < 7; i++) { threadPool.execute(runnable); } //關(guān)閉線程池 threadPool.shutdown(); } } 輸出結(jié)果: pool-1-thread-1==>執(zhí)行任務(wù) pool-1-thread-4==>執(zhí)行任務(wù) pool-1-thread-2==>執(zhí)行任務(wù) pool-1-thread-2==>執(zhí)行任務(wù) pool-1-thread-3==>執(zhí)行任務(wù) pool-1-thread-4==>執(zhí)行任務(wù) pool-1-thread-1==>執(zhí)行任務(wù)
從結(jié)果中可以看出吉嫩,啟動了最大線程來執(zhí)行任務(wù)价认。
當(dāng)任務(wù)數(shù)大于最大線程數(shù)時:
public class ThreadPoolExecutorTest { public static void main(String[] args) { // 創(chuàng)建線程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); //創(chuàng)建任務(wù) Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "==>執(zhí)行任務(wù)"); } }; // 執(zhí)行任務(wù) for (int i = 0; i < 8; i++) { threadPool.execute(runnable); } //關(guān)閉線程池 threadPool.shutdown(); } } 輸出結(jié)果: 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==>執(zhí)行任務(wù) pool-1-thread-4==>執(zhí)行任務(wù) pool-1-thread-4==>執(zhí)行任務(wù) pool-1-thread-4==>執(zhí)行任務(wù) pool-1-thread-2==>執(zhí)行任務(wù) pool-1-thread-3==>執(zhí)行任務(wù) pool-1-thread-1==>執(zhí)行任務(wù)
從結(jié)果中可以看出,任務(wù)大于最大線程數(shù)自娩,使用拒絕策略直接拋出異常用踩。
-
三、總結(jié)
本文介紹了三種線程的創(chuàng)建方式:
- 自定義類繼承Thread類并重寫run方法創(chuàng)建
- 自定義類實現(xiàn)Runnable接口并重寫run方法創(chuàng)建
- 實現(xiàn)Callable接口創(chuàng)建
介紹了七種線程池的創(chuàng)建方式:
- 使用newFixedThreadPool方法創(chuàng)建線程池
- 使用newCachedThreadPool方法創(chuàng)建線程池
- 使用newSingleThreadExecutor的方法創(chuàng)建線程池
- 使用newScheduledThreadPool的方法創(chuàng)建線程池
- 使用newSingleThreadScheduledExecutor方法創(chuàng)建線程池
- 使用newWorkStealingPool方法創(chuàng)建線程池
- 使用ThreadPoolExecutor創(chuàng)建線程池