阿里巴巴規範說過,使用線程最好是用線程池,那就是說使用線程池有一定的好處,能夠管理線程連接,開啓用戶使用的線程數,用完迴歸池中,可以讓其他線程使用,減少連接線程的資源消耗。
那麼Java中有提供ThreadPoolExecutor線程池的類實現,Java也對其封裝了Executors的四種靜態使用方法,先來講一下四種線程池的使用。
1.newFixedThreadPool
fixed的意思就是固定, 見名知意就是創建一個固定容量的線程池,用戶傳要創建幾個線程,那麼所有的任務都由這幾個線程來工作。代碼示例如下:
這個代碼模擬了開3個線程處理30個任務,看一下輸出結果
public static void FixedThreadPoolDemo() throws InterruptedException {
//創建一個定長線程池
//定長線程池的特點:固定線程總數,空閒線程用於執行任務,如果線程都在使用的話,後續任務處於等待狀態
//在線程池中的線程執行任務後在執行後續的任務
//如果任務處於等待的狀態,備選的等待算法默認爲(FIFO(先進先出))也可以設置LIFO(後進先出)
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 30; i++) {
final int index = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("操作的線程:"+Thread.currentThread().getName()+" 索引:"+index);
//list.add(Thread.currentThread().getName().split("-")[3]);
}
});
}
Thread.sleep(1000);
// OptionalInt max = list.stream().mapToInt(str -> Integer.parseInt(str.toString())).max();
//shutdown()代表關閉線程池(等待所有線程完成)
//shutdownNow()代表立即終止線程池的運行,不等待線程,不推薦使用
executorService.shutdown();
public static void main(String[] args) throws InterruptedException {
FixedThreadPool.FixedThreadPoolDemo();
}
可以看出線程使用一直都是1,2,3個線程,不會在額外開闢線程
點進去看newFixedThreadPool方法,內部使用的ThreadPoolExecutor類創建線程池,講一下參數把。
第1個參數:核心線程數,傳的是3,這裏nThreads就是那個3,
第2個參數:最大線程數也設置的是nThreads,代表我們所開的線程最大的也就是3個,
第3個參數:keepAliveTime最大線程數空閒存活時間,這裏的0L相當於空閒了立即銷燬最大線程數,但是我們的核心線程數也是3,所以不存在銷燬
第4個參數:基於第3個參數,是它的單位
第5個參數:隊列,用於等待線程處理的任務隊列存儲。這裏用的LinkedBlockingQueue,阻塞的無界隊列
2.newCachedThreadPool
還是見名知意可緩存線程池,用戶不傳要開多少個線程,根據任務進行分配線程,線程數量可能會很多,代碼示例如下:
模擬200個任務進行操作,看看會開多少個線程
public static void cacheThreadDemo() throws InterruptedException {
//創建一個可緩存線程池
//ExecutorService調度器對象,用於管理線程池
//Executors.newCachedThreadPool()創建一個可緩存線程池
//可緩存線程池的特點:無限大,如果線程中沒有可用的則創建,有空閒線程則利用起來
ExecutorService executorService = Executors.newCachedThreadPool();
// List list = new ArrayList();
int[] strs = new int[400];
for (int i = 1; i <= 200; i++) {
final int index = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("操作的線程:"+Thread.currentThread().getName() + " 索引:" + index);
//list.add(Thread.currentThread().getName().split("-")[3]);
strs[index] = Integer.parseInt(Thread.currentThread().getName().split("-")[3]);
}
});
}
Thread.sleep(1000);
int max = Arrays.stream(strs).max().getAsInt();
System.out.println("開闢空間最大的線程池名稱是:" + max);
executorService.shutdown();
}
public static void main(String[] args) throws InterruptedException {
CachedThreadPool.cacheThreadDemo();
}
有開闢的線程,有重用的線程,本次示例開了79個。
內部調用ThreadPoolExecutor,核心線程數是0,最大線程無止盡,直到內存用完,最大線程池空閒存活時間60秒,等待的隊列是使用SynchronousQueue,SynchronousQueue是一個內部只能包含一個元素的隊列,插入元素到隊列的線程被阻塞,直到另一個線程從隊列中獲取了隊列中存儲的元素
3.newSingleThreadExecutor
見名知意,是單例,只會創建一個線程處理任務。代碼示例如下:
public static void singleThreadDemo() throws InterruptedException {
//創建一個單線程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 1; i <= 6; i++) {
final int index = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + index);
//list.add(Thread.currentThread().getName().split("-")[3]);
}
});
}
Thread.sleep(1000);
executorService.shutdown();
}
public static void main(String[] args) throws InterruptedException {
singlePool.singleThreadDemo();
}
結果如下所示:只會創造一個線程來處理任務。
實現是核心線程數設置爲1,最大線程池數設置爲1,線程池存活時間是空閒立即銷燬,隊列是阻塞無界隊列
4.newScheduledThreadPool
名字裏有任務調度,一般java是定期執行一些事情,那麼這裏也是,延遲定期執行事務,示例代碼如下:
public static void scheduledPoolDemo() {
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
// 延遲1秒執行,每3秒執行一次
scheduledPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("操作線程:" + Thread.currentThread().getName() + " " + new Date() + "延遲1秒執行,每3秒執行一次");
}
}, 1, 3, TimeUnit.SECONDS);
}
public static void main(String[] args) {
scheduledPoolDemo();
}
執行結果如下:
咱們的核心線程數是5個,這裏最大線程數是無數個,0秒最大線程空閒,隊列採用延遲隊列方式。
ThreadPoolExecutor講解
那麼這4大線程池介紹完畢了,在介紹下ThreadPoolExecutor,阿里巴巴規範說創建線程池最好用ThreadPoolExecutor,而不要用上述說的4個,原因是自己實現的可以設置隊列爲不是無界隊列,也能控制核心線程數和最大核心線程數,根據自己的業務場景選擇合適的參數。下面來講講7大參數,
1.corePoolSize:核心線程數,當有任務進來時還未達到核心線程數,則創建核心線程
1.1 當線程數沒有達到核心線程數最大值的時候,新任務會繼續創建線程,不會複用線程池的線程
1.2 核心線程一般不會銷燬,空閒也不會銷燬,除非通過方法allowCoreThreadTimeOut(boolean value)設置爲true時,超時也 同樣會被銷燬
1.3 生產環境首次初始化的時候,可以調用prestartCoreThread(),prestartAllCoreThread()來預先創建所有的和核心線程,避免 第一次調用緩慢。
2.maximumPoolSize:最大線程數,線程池中能夠容納(包含核心線程數)同時執行的最大線程數,此知大於等於1
3.keepAliveTime:最大線程池中線程數量超過核心線程數時,多餘的空閒線程存活的時間,當空閒時間達到keepAliveTime時,多餘線程就會被(最大線程池的線程)銷燬,當keepAliveTime設置爲0時,表明最大線程池空閒立即銷燬。
4.unit:keepAliveTime的單位
5.workQueue:等待執行的任務隊列,核心線程滿了,新的任務就會在加入等待隊列中。
6.threadFactory:表示生成線程池中工作線程的線程工廠,用於創建線程,一般默認即可。
7.handler:拒絕策略,沒有空閒的線程處理任務,並且等待隊列已滿,再有新任務進來如何來拒絕請求的runnable的策略。
拒絕策略還蠻重要的,記住這四個不同的拒絕策略:
7.1 AbortPolicy:直接拋出RejectedExecutionException異常阻止系統正常運行,默認使用的異常。
7.2 DiscardPolicy:默默丟棄無法處理的任務,不予任何處理也不拋出異常。
7.3 DiscardOldestPolicy:拋棄隊列中等待最久的任務,然後把當前任務加入隊列中嘗試再次提交當前任務。
7.4 CallerRunsPolicy:"調用者運行"一種調節機制,該策略既不會拋棄任務,也不會拋出異常,二是將某些任務回退到調用者,從而降低新任務流量。
自定義的線程池代碼示例demo
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
5,
2L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3), //不寫的話默認也是Integer.MAX_VALUE
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());//默認的拒絕策略
try {
for (int i = 0; i <8; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "辦理業務");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //關閉線程池
}
}
結果:
線程池執行流程
1.提交任務時,檢查覈心線程池是否已滿,沒有滿則創建線程,核心線程池滿了走2
2.檢查隊列是否已滿,沒有則把任務放入隊列中,隊列滿了走3
3.檢查最大線程池數是否已滿,沒有則繼續創建線程執行任務,滿了走4
4.執行拒絕策略
以上就是線程池的全部內容,希望可以幫到你!下節抽空會講下隊列哦!