一篇文章帶你了解如何正確使用java線程池
1、線程是不是越多越好?
在學(xué)習(xí)多線程之前,讀者可能會(huì)有疑問(wèn)?如果單線程跑得太慢,那么是否就能多創(chuàng)建多個(gè)線程來(lái)跑任務(wù)?并發(fā)的情況,線程是不是創(chuàng)建越多越好?這是一個(gè)很經(jīng)典的問(wèn)題,畫圖表示一下創(chuàng)建很多線程的情況,然后進(jìn)行情況分析。

- 創(chuàng)建線程和銷毀線程都是需要時(shí)間的,如果創(chuàng)建時(shí)間+銷毀時(shí)間>執(zhí)行任務(wù)時(shí)間就很不劃算
- 創(chuàng)建后的線程是需要內(nèi)存去存放的,創(chuàng)建的線程對(duì)應(yīng)一個(gè)Thread對(duì)象,對(duì)象是會(huì)占用JVM的堆內(nèi)存的,根據(jù)jvm規(guī)范,一個(gè)線程默認(rèn)最大棧大小為1M,這個(gè)??臻g也是需要從系統(tǒng)內(nèi)存中分配的,所以線程越多,需要的內(nèi)存就越多
- 創(chuàng)建線程,操作系統(tǒng)是需要頻繁進(jìn)行線程上下文切換的,所以線程創(chuàng)建太多,是會(huì)影響性能的
上下文切換(context switch):對(duì)于單核CPU來(lái)說(shuō),在一個(gè)時(shí)刻只能運(yùn)行一個(gè)線程,對(duì)于并行來(lái)說(shuō),單核cpu也是可以支持多線程執(zhí)行代碼的,CPU是通過(guò)給線程分配時(shí)間片來(lái)解決的,所謂時(shí)間片是CPU給每個(gè)線程分配的時(shí)間,時(shí)間片的時(shí)間是非常短的,所以執(zhí)行完成一個(gè)時(shí)間片后,進(jìn)行任務(wù)切換,切換之前先保存這個(gè)任務(wù)的狀態(tài),以便于下次換回來(lái)的時(shí)候,可以加載這個(gè)任務(wù)的狀態(tài),所以從保存任務(wù)狀態(tài)到再加載任務(wù)的過(guò)程稱為上下文切換,不僅在線程間可以上下文切換,進(jìn)程也同樣可以
2、如何正確使用多線程?
- 如果是計(jì)算型任務(wù)?
CPU數(shù)量的1~2倍即
- 可如果是IO密集型任務(wù)?
就需要多一些線程,要根據(jù)具體的io阻塞時(shí)長(zhǎng)來(lái)進(jìn)行考量決定
3、Java線程池的工作原理

- 接收任務(wù),放入線程池的任務(wù)倉(cāng)庫(kù)
- 工作線程從線程池的任務(wù)倉(cāng)庫(kù)取,執(zhí)行
- 沒(méi)有任務(wù)時(shí),線程阻塞,有任務(wù)時(shí)喚醒線程
4、掌握J(rèn)UC線程池API
- Executor : 接口類
- ExecutorService:加入關(guān)閉方法和對(duì)Runnable、Callable、Future的支持

shutdown:已經(jīng)提交的會(huì)執(zhí)行完成shutdownNow:正在執(zhí)行的會(huì)執(zhí)行完成,未來(lái)執(zhí)行的返回awaitTermination:阻塞等待任務(wù)關(guān)閉完成submit類型的:都是提交任務(wù)的,支持Runnable和CallableinvokeAll類型的:執(zhí)行集合中所有任務(wù)
ScheduleExecutorService :加入對(duì)定時(shí)任務(wù)的支持

其中schedule(Runablle , long, Timeunit)和schedule(Callable<V> , long, TimeUnit)表示的是多久后執(zhí)行,而scheduleAtFixedRate方法和scheduleWithFixedDelay方法表示的都是周期性重復(fù)執(zhí)行的
再描述scheduleAtFixedRate方法和scheduleWithFixedDelay方法的區(qū)別:
scheduleAtFixedRate:以固定的時(shí)間頻率重復(fù)執(zhí)行任務(wù),如每10s ,也就是兩個(gè)任務(wù)直接以固定的時(shí)間間隔執(zhí)行,不管任務(wù)執(zhí)行完成與否

scheduleWithFixedDelay:以固定的任務(wù)時(shí)延遲來(lái)重復(fù)執(zhí)行任務(wù),這種任務(wù)不管任務(wù)執(zhí)行多久都執(zhí)行完成,然后隔預(yù)定的如3s,接著執(zhí)行下一個(gè)任務(wù),每個(gè)任務(wù)之間的間隔都是一樣的

Executors:快速得到線程池的工具類,創(chuàng)建線程池的工廠類
- newFixedThreadPool(int nThreads):創(chuàng)建一個(gè)固定大小、任務(wù)隊(duì)列 無(wú)界的線程池。線程池的核心線程數(shù)=最大線程池=nThreads
- newCachedThreadPool():創(chuàng)建的是一個(gè)大小無(wú)界的緩沖線程池。它的任務(wù)隊(duì)列是一個(gè)同步隊(duì)列。如果隊(duì)列中有空閑的線程,則用空閑線程執(zhí)行,如果沒(méi)有就創(chuàng)建新線程執(zhí)行。池中線程空閑超過(guò)60s,就會(huì)被釋放。緩沖線程池使用于執(zhí)行耗時(shí)比較小的異步任務(wù)。線程池的核心線程數(shù)=0,最大線程池=Integer.MAX_VALUE
- newSingleThreadExecutor():創(chuàng)建的是只有一個(gè)線程來(lái)執(zhí)行無(wú)界任務(wù)隊(duì)列的單一線程池。該線程池按順序執(zhí)行一個(gè)一個(gè)加入的任務(wù),任何時(shí)刻都只有一個(gè)線程在執(zhí)行。單一線程池和newFixedThreadPool(1)的區(qū)別在于,單一線程池的池大小是不能再改變的
- newScheduleThreadPool(int corePoolSize): 能定時(shí)執(zhí)行任務(wù)的線程池,該池的核心線程數(shù)由參數(shù)corePoolSize指定,最大線程數(shù)=Integer.MAX_VALUE
- newWorkStealingPool():以當(dāng)前系統(tǒng)可用的處理器數(shù)作為并行級(jí)別創(chuàng)建的work-stealing thread pool(ForkJoinPool)
- newWorkStealingPool(int parallelism):以指定的parallelism并行級(jí)別創(chuàng)建的work-stealing thread pool(ForkJoinPool)
ThreadPoolExecutor:線程池的標(biāo)準(zhǔn)實(shí)現(xiàn)

下面列舉出ThreadPoolExecutor的主要參數(shù):
| 參數(shù) | 描述 |
|---|---|
| corePoolSize | 核心線程數(shù)量 |
| maxPoolSize | 最大線程數(shù)量 |
| keepAliveTime+時(shí)間單位 | 空閑線程的存活時(shí)間 |
| ThreadFactory | 線程工廠,用于創(chuàng)建線程 |
| workQueue | 用于存放任務(wù)的隊(duì)列,可以稱之為工作隊(duì)列 |
| Handler | 用于處理被拒絕的任務(wù) |
雖然Executors使用起來(lái)很方便,不過(guò)在阿里編程規(guī)范里是強(qiáng)調(diào)了慎用Executors創(chuàng)建線程池,下面摘錄自阿里編程規(guī)范手冊(cè):
【強(qiáng)制】線程池不允許使用Executors去創(chuàng)建,而是通過(guò)ThreadPoolExecutor的方式,
這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
說(shuō)明:Executors各個(gè)方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要問(wèn)題是堆積的請(qǐng)求處理隊(duì)列可能會(huì)耗費(fèi)非常大的內(nèi)存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要問(wèn)題是線程數(shù)最大數(shù)是Integer.MAX_VALUE,可能會(huì)創(chuàng)建數(shù)量非常多的線程,甚至OOM。
ThreadPoolExecutor的基本參數(shù):
new ThreadPoolExecutor(
2, // 核心線程數(shù)
5, // 最大線程數(shù)
60L, // keepAliveTime,線程空閑超過(guò)這個(gè)數(shù),就會(huì)被銷毀釋放
TimeUnit.SECONDS, // keepAliveTime的時(shí)間單位
new ArrayBlockingQueue(5)); // 傳入邊界為5的工作隊(duì)列
畫流程圖表示,線程池的核心參數(shù)是corePoolSize、maxPoolSize、workQueue(工作隊(duì)列)

線程池工作原理示意圖:任務(wù)可以一直放,直到線程池滿了的情況,才會(huì)拒絕,然后除了核心線程,其它的線程會(huì)被合理回收。所以正常情況下,線程池中的線程數(shù)量會(huì)處在 corePoolSize 與 maximumPoolSize 的閉區(qū)間內(nèi)

ThreadPoolExecutor基本實(shí)例:
ExecutorService service = new ThreadPoolExecutor(2, 5,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue(5));
service.execute(() ->{
System.out.println(String.format("thread name:%s",Thread.currentThread().getName()));
});
// 避免內(nèi)存泄露,記得關(guān)閉線程池
service.shutdown();
ThreadPoolExecutor加上Callable、Future使用的例子:
public static void main(String[] args) {
ExecutorService service = new ThreadPoolExecutor(2, 5,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue(5));
Future<Integer> future = service.submit(new CallableTask());
Thread.sleep(3000);
System.out.println("future is done?" + future.isDone());
if (future.isDone()) {
System.out.println("callableTask返回參數(shù):"+future.get());
}
service.shutdown();
}
static class CallableTask implements Callable<Integer>{
@Override
public Integer call() {
return ThreadLocalRandom.current().ints(0, (99 + 1)).limit(1).findFirst().getAsInt();
}
}
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
MyBatis-Flex BaseMapper的接口基本用法小結(jié)
本文主要介紹了MyBatis-Flex BaseMapper的接口基本用法小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02
SpringBoot整合MongoDB實(shí)現(xiàn)文件上傳下載刪除
這篇文章主要介紹了SpringBoot整合MongoDB實(shí)現(xiàn)文件上傳下載刪除的方法,幫助大家更好的理解和學(xué)習(xí)使用SpringBoot框架,感興趣的朋友可以了解下2021-05-05
Java并發(fā)Futures和Callables類實(shí)例詳解
Callable對(duì)象返回Future對(duì)象,該對(duì)象提供監(jiān)視線程執(zhí)行的任務(wù)進(jìn)度的方法, Future對(duì)象可用于檢查Callable的狀態(tài),然后線程完成后從Callable中檢索結(jié)果,這篇文章給大家介紹Java并發(fā)Futures和Callables類的相關(guān)知識(shí),感興趣的朋友一起看看吧2024-05-05
Java編程中實(shí)現(xiàn)歸并排序算法的實(shí)例教程
這篇文章主要介紹了Java編程中實(shí)現(xiàn)歸并排序算法的實(shí)例教程,包括自底向上的歸并排序的實(shí)現(xiàn)方法介紹,需要的朋友可以參考下2016-05-05
java實(shí)現(xiàn)超市商品庫(kù)存管理平臺(tái)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)超市商品庫(kù)存管理平臺(tái),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10
實(shí)例講解Java中動(dòng)態(tài)代理和反射機(jī)制
在本篇文章里小編給各位分享了關(guān)于Java中動(dòng)態(tài)代理和反射機(jī)制的相關(guān)知識(shí)點(diǎn)內(nèi)容,有需要的朋友們學(xué)習(xí)下。2019-01-01
基于Java實(shí)現(xiàn)獲取本地IP地址和主機(jī)名
這篇文章主要介紹了基于Java實(shí)現(xiàn)獲取本地IP地址和主機(jī)名,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
Java實(shí)現(xiàn)兩人五子棋游戲(四) 落子動(dòng)作的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)兩人五子棋游戲,落子動(dòng)作的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
testNG項(xiàng)目通過(guò)idea Terminal命令行執(zhí)行的配置過(guò)程
這篇文章主要介紹了testNG項(xiàng)目通過(guò)idea Terminal命令行執(zhí)行,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07

