我記得之前在面試的時候,面試官問我單線程池有什么意義?我跟面試官說:雖然是單線程池,但提供了工作隊(duì)列,生命周期管理,工作線程維護(hù)等功能。
雖然有點(diǎn)籠統(tǒng),但是誰又能說我說的有錯呢,單線程,無論是在面試得過程中還是日常開發(fā),都算是一個很重要的知識點(diǎn),今天沒什么事情,我就結(jié)合源碼+手寫得案例,帶大家看一下線程吃的7種創(chuàng)建方式
文章首發(fā)公眾號:Java架構(gòu)師聯(lián)盟,每日更新技術(shù)好文
1.FixedThreadPool
創(chuàng)建一個固定大小的線程池,可控制并發(fā)的線程數(shù),超出的線程會在隊(duì)列中等待。
源碼
使用示例如下:
package com.test.thread;
?
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
?
/**
* @author :biws
* @date :Created in 2020/12/17 19:23
* @description:測試FixedThreadPool
*/
public class testThread1 {
public static void fixedThreadPool() {
// 創(chuàng)建 2 個數(shù)據(jù)級的線程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
?
// 創(chuàng)建任務(wù)
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("任務(wù)被執(zhí)行,線程:" + Thread.currentThread().getName());
}
};
?
// 線程池執(zhí)行任務(wù)(一次添加 4 個任務(wù))
// 執(zhí)行任務(wù)的方法有兩種:submit 和 execute
threadPool.submit(runnable); // 執(zhí)行方式 1:submit
threadPool.execute(runnable); // 執(zhí)行方式 2:execute
threadPool.execute(runnable);
threadPool.execute(runnable);
?
?
}
?
public static void main(String[] args) {
fixedThreadPool();
}
}
執(zhí)行結(jié)果如下:
后來我將測試數(shù)量提交到100,而線程池中處理線程得數(shù)量增加到4
執(zhí)行結(jié)果
最開始,提交4個線程執(zhí)行,之后的線程會在隊(duì)列中排序等待被執(zhí)行
如果覺得以上方法比較繁瑣,還可以用更簡單的使用方法,如下代碼所示:
package com.test.thread;
?
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
?
/**
* @author :biws
* @date :Created in 2020/12/17 19:27
* @description:FixedThreadPool進(jìn)階寫法
*/
public class testThreadG {
public static void fixedThreadPool() {
// 創(chuàng)建線程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
/*
* 執(zhí)行任務(wù)
* 為了個之前得進(jìn)行統(tǒng)一,所以這里通過for循環(huán),同樣是提交4個執(zhí)行*/
for(int i=1;i<=4;i++) {
threadPool.execute(() -> {
System.out.println("任務(wù)被執(zhí)行,線程:" + Thread.currentThread().getName());
});
}
}
?
public static void main(String[] args) {
fixedThreadPool();
}
}
2.CachedThreadPool
創(chuàng)建一個可緩存的線程池,若線程數(shù)超過處理所需,緩存一段時間后會回收,若線程數(shù)不夠,則新建線程。
源碼
參數(shù)詳解:
corePoolSize = 0,
maximumPoolSize設(shè)置為Integer.MAX_VALUE,代表沒有核心線程,非核心線程是無界的;keepAliveTime = 60L,空閑線程等待新任務(wù)的最長時間是60s;
用了阻塞隊(duì)列SynchronousQueue,是一個不存儲元素的阻塞隊(duì)列
使用示例如下:
package com.test.thread;
?
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
?
/**
* @author :biws
* @date :Created in 2020/12/17 19:38
* @description:測試CachedThreadPool
*/
public class testThread2 {
public static void cachedThreadPool() {
// 創(chuàng)建線程池
ExecutorService threadPool = Executors.newCachedThreadPool();
// 執(zhí)行任務(wù)
for (int i = 0; i < 10; i++) {
threadPool.execute(() -> {
System.out.println("任務(wù)被執(zhí)行,線程:" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
});
}
}
?
public static void main(String[] args) {
cachedThreadPool();
}
}
}
執(zhí)行結(jié)果如下:
從上述結(jié)果可以看出,線程池創(chuàng)建了 10 個線程來執(zhí)行相應(yīng)的任務(wù)。
而如果我將sleep注釋之后,再來看執(zhí)行結(jié)果
我想這個結(jié)果一目了然吧,大家如果可以的話,可以把這個執(zhí)行數(shù)據(jù)提升一下,然后再查看一下結(jié)果應(yīng)該會更明顯
3.SingleThreadExecutor
創(chuàng)建單個線程數(shù)的線程池,它可以保證先進(jìn)先出的執(zhí)行順序。為了能讓大家看的更清楚,所以每個線程在執(zhí)行得時候我都添加了他的時間
使用示例如下:
package com.test.thread;
?
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
?
/**
* @author :biws
* @date :Created in 2020/12/17 20:17
* @description:測試singleThreadExecutor
*/
public class testThread4 {
?
public static void singleThreadExecutor() {
// 創(chuàng)建線程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 執(zhí)行任務(wù)
for (int i = 0; i < 10; i++) {
final int index = i;
?
threadPool.execute(() -> {
new Thread();
System.out.println(Thread.currentThread().getName()+"開始時間[" + new java.util.Date().getTime());
System.out.println(index + ":任務(wù)被執(zhí)行");
?
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"結(jié)束時間[" + new java.util.Date().getTime());
});
}
}
?
public static void main(String[] args) {
singleThreadExecutor();
?
}
}
執(zhí)行結(jié)果如下:
4.ScheduledThreadPool
創(chuàng)建一個可以執(zhí)行延遲任務(wù)的線程池。
源碼
這個線程得源碼有一點(diǎn)特殊,由兩部分組成,第一部分
這里創(chuàng)建了ScheduledThreadPoolExecutor,ScheduledThreadPoolExecutor繼承自ThreadPoolExecutor,主要用于給定 延時之后的運(yùn)行任務(wù)或定期處理任務(wù)
第二部分
使用示例如下:
package com.test.thread;
?
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
?
/**
* @author :biws
* @date :Created in 2020/12/17 20:43
* @description:測試ScheduledThreadPool
*/
public class testThread3 {
public static void scheduledThreadPool() {
// 創(chuàng)建線程池
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
// 添加定時執(zhí)行任務(wù)(1s 后執(zhí)行)
for(int i=0;i<4;i++){
final int index=i;
?
System.out.println(index+"添加任務(wù),時間:" + new Date()+Thread.currentThread().getName());
threadPool.schedule(() -> {
System.out.println(index+"任務(wù)被執(zhí)行,時間:" + new Date()+Thread.currentThread().getName());
/* try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}*/
}, 1, TimeUnit.SECONDS);
}
}
public static void main(String[] args) {
scheduledThreadPool();
}
}
執(zhí)行結(jié)果如下:
5.SingleThreadScheduledExecutor
創(chuàng)建一個單線程的可以執(zhí)行延遲任務(wù)的線程池。
使用示例如下:
package com.test.thread;
?
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
?
/**
* @author :biws
* @date :Created in 2020/12/17 20:54
* @description:測試SingleThreadScheduledExecutor
*/
public class testThread5 {
public static void SingleThreadScheduledExecutor() {
// 創(chuàng)建線程池
ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
// 添加定時執(zhí)行任務(wù)(2s 后執(zhí)行)
for (int i=0;i<4;i++) {
?
final int index=i;
System.out.println(index+"添加任務(wù),時間:" + new Date());
threadPool.schedule(() -> {
System.out.println(index+"任務(wù)被執(zhí)行,時間:" + new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}, 2, TimeUnit.SECONDS);
}
}
?
public static void main(String[] args) {
SingleThreadScheduledExecutor();
}
}
執(zhí)行結(jié)果如下:
從上述結(jié)果可以看出,任務(wù)在 2 秒之后被執(zhí)行了,符合我們的預(yù)期。
6.newWorkStealingPool
創(chuàng)建一個搶占式執(zhí)行的線程池(任務(wù)執(zhí)行順序不確定)
注意:
此方法只有在 JDK 1.8+ 版本中才能使用。
使用示例如下:
public static void workStealingPool() {
// 創(chuàng)建線程池
ExecutorService threadPool = Executors.newWorkStealingPool();
// 執(zhí)行任務(wù)
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(() -> {
System.out.println(index + " 被執(zhí)行,線程名:" + Thread.currentThread().getName());
});
}
// 確保任務(wù)執(zhí)行完成
while (!threadPool.isTerminated()) {
}
}
執(zhí)行結(jié)果如下:
7.ThreadPoolExecutor
最原始的創(chuàng)建線程池的方式,它包含了 7 個參數(shù)可供設(shè)置。
使用示例如下:
package com.test.thread;
?
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
?
/**
* @author :biws
* @date :Created in 2020/12/17 21:07
* @description:測試ThreadPoolExecutor
*/
public class testThread7 {
public static void myThreadPoolExecutor() {
// 創(chuàng)建線程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
// 執(zhí)行任務(wù)
for (int i = 0; i < 10; i++) {
final int index = i;
threadPool.execute(() -> {
System.out.println(index + " 被執(zhí)行,線程名:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
?
public static void main(String[] args) {
myThreadPoolExecutor();
}
}
執(zhí)行結(jié)果如下:
ThreadPoolExecutor 參數(shù)介紹
就像我前面説的,ThreadPoolExecutor 是可以設(shè)置一些參數(shù)的,在我的代碼中,我只設(shè)置了這幾個參數(shù)
在源碼中是這樣編寫的
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
7 個參數(shù)代表的含義如下:
corePoolSize
核心線程數(shù),線程池中始終存活的線程數(shù)。
maximumPoolSize
最大線程數(shù),線程池中允許的最大線程數(shù),當(dāng)線程池的任務(wù)隊(duì)列滿了之后可以創(chuàng)建的最大線程數(shù)。
keepAliveTime \ unit:
最大線程數(shù)可以存活的時間,當(dāng)線程中沒有任務(wù)執(zhí)行時,最大線程就會銷毀一部分,最終保持核心線程數(shù)量的線程。
單位是和參數(shù) 3 存活時間配合使用的,合在一起用于設(shè)定線程的存活時間
threadFactory
線程工廠,主要用來創(chuàng)建線程,默認(rèn)為正常優(yōu)先級、非守護(hù)線程。
workQueue
一個阻塞隊(duì)列,用來存儲線程池等待執(zhí)行的任務(wù),均為線程安全,它包含以下 7 種類型:
ArrayBlockingQueue:一個由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列。
LinkedBlockingQueue:一個由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列。
SynchronousQueue:一個不存儲元素的阻塞隊(duì)列,即直接提交給線程不保持它們。
PriorityBlockingQueue:一個支持優(yōu)先級排序的無界阻塞隊(duì)列。
DelayQueue:一個使用優(yōu)先級隊(duì)列實(shí)現(xiàn)的無界阻塞隊(duì)列,只有在延遲期滿時才能從中提取元素。
LinkedTransferQueue:一個由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列。與SynchronousQueue類似,還含有非阻塞方法。
LinkedBlockingDeque:一個由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。
較常用的是 LinkedBlockingQueue 和 Synchronous,線程池的排隊(duì)策略與 BlockingQueue 有關(guān)。
handler
拒絕策略,拒絕處理任務(wù)時的策略,系統(tǒng)提供了 4 種可選:
默認(rèn)策略為 AbortPolicy。
代碼演示
。。。。。
// 創(chuàng)建線程,線程的任務(wù)隊(duì)列的長度為 1
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
new ThreadPoolExecutor.DiscardPolicy());
。。。。
//在Java種共有4種拒絕策略
/*
AbortPolicy:拒絕并拋出異常。
CallerRunsPolicy:使用當(dāng)前調(diào)用的線程來執(zhí)行此任務(wù)。
DiscardOldestPolicy:拋棄隊(duì)列頭部(最舊)的一個任務(wù),并執(zhí)行當(dāng)前任務(wù)。
DiscardPolicy:忽略并拋棄當(dāng)前任務(wù)。
*/</pre>
演示結(jié)果
我們創(chuàng)建了一個核心線程數(shù)和最大線程數(shù)都為 1 的線程池,并且給線程池的任務(wù)隊(duì)列設(shè)置為 1,這樣當(dāng)我們有 2 個以上的任務(wù)時就會觸發(fā)拒絕策略,
自定義拒絕策略
除了 Java 自身提供的 4 種拒絕策略之外,我們也可以自定義拒絕策略,示例代碼如下:
。。。
// 創(chuàng)建線程,線程的任務(wù)隊(duì)列的長度為 1
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 執(zhí)行自定義拒絕策略的相關(guān)操作
System.out.println("我是自定義拒絕策略~");
}
});
。。。
程序的執(zhí)行結(jié)果如下:
線程池的執(zhí)行流程
提交一個任務(wù)到線程池中,線程池的處理流程如下:
1、判斷線程池里的核心線程是否都在執(zhí)行任務(wù),如果不是(核心線程空閑或者還有核心線程沒有被創(chuàng)建)則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù)。如果核心線程都在執(zhí)行任務(wù),則進(jìn)入下個流程。
2、線程池判斷工作隊(duì)列是否已滿,如果工作隊(duì)列沒有滿,則將新提交的任務(wù)存儲在這個工作隊(duì)列里。如果工作隊(duì)列滿了,則進(jìn)入下個流程。
3、判斷線程池里的線程是否都處于工作狀態(tài),如果沒有,則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù)。如果已經(jīng)滿了,則交給飽和策略來處理這個任務(wù)。
總結(jié)
今天,我花費(fèi)90分鐘得時間,一點(diǎn)點(diǎn)得手敲案例,將7個線程池的創(chuàng)建方式進(jìn)行講解,但是在日常得生活中,我應(yīng)用最多的就是hreadPoolExecutor,因?yàn)樗膮?shù)以及拒絕策略有時會讓開發(fā)過程更加可控一些,并且支持自定義,這就很6
希望本文的內(nèi)容能幫助到你。原創(chuàng)不易,覺得不錯就點(diǎn)個贊再走吧!