線程池的優(yōu)勢:
- 通過復(fù)用已有的線程,降低線程創(chuàng)建的銷毀的系統(tǒng)開銷
- 提高響應(yīng)速度,復(fù)用已有的線程避免了創(chuàng)建線程的開銷
- 方便線程數(shù)量的管控,如果創(chuàng)建的線程過多,咋可能導(dǎo)致系統(tǒng)化新能的下降或者oom的發(fā)生。、
- 線程池提供了定時等功能,并且方便創(chuàng)建
我們可以使用new ThreadPoolExecutor()來創(chuàng)建一個線程池
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize 這個參數(shù)意思是核心線程的數(shù)量,他們一直存在于線程池中,即使處于閑置狀態(tài)也不會被銷毀,除非設(shè)置了allowCoreThreadTimeOut這個參數(shù),則在閑置狀態(tài)超過了這個參數(shù)keepAliveTime 則被銷毀
- maximumPoolSize 線程池中的最大線程數(shù)量,如果活動的線程數(shù)超過這個值的話,后續(xù)的線程就會被阻塞。 最大數(shù)量指的是核心線程數(shù)量和非核心線程數(shù)量
- keepAliveTime 非核心線程閑置存在的時長,意思當非核心線程閑置時間超過這個值就會被回收
- TimeUnit 是keepAliveTime的時間單位,是個枚舉類型,包含TimeUnit.MILLISECONDS 毫秒,SECONDS秒,天等等
-
workQueue 阻塞隊列。 所有提交的Runnable線程都存放到該隊列中,常用的阻塞隊列有:image
- threadFactory 線程 工廠,為線程池提供線程的創(chuàng)建,一般使用默認的即可Executors.defaultThreadFactory()
- handler 當任務(wù)隊列已滿,并且線程池達到了最大線程數(shù)的時候規(guī)定了接下來的線程該如何處理。
image
ThreadPoolExecutor提供了多鐘構(gòu)造方法,我們可以使用他默認的一些方法來創(chuàng)建,比如:
ThreadPoolExecutor executor = new
ThreadPoolExecutor(
10, 10, 1000, TimeUnit.SECONDS,
new LinkedBlockingDeque<>());
ThreadPoolExecutor 提供了execute和submit兩個方法提交線程任務(wù),比如:
executor.submit(()->{});
executor.execute(()->{});
但是submit提供了一分返回值,所以如果們用Callable的話,那么我們就可以獲得一個返回值,如果不知道Callable請看我的另外一篇文章 《Java第三種線程創(chuàng)建方法》
Future<String> submit = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "";
}
});
submit.get();
如果使用Lambda更簡單:
Future<String> submit = executor.submit(() -> "")
submit.get(); 可以得到call中返回的值。
其實系統(tǒng)為我們提供了集中線程池的創(chuàng)建,所以不用直接使用ThreadPoolExecutor這個類來創(chuàng)建線程池:
newFixedThreadPool
Executors.newFixedThreadPool(2);
//源碼
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newFixedThreadPool,固定線程數(shù)量的線程池,核心線程池數(shù)和非核心線程數(shù)量相等并且由自己定義,不存在超時機制,任務(wù)隊列理論是上可以無限大
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
可以看到 核心線程數(shù)為0,而線程池的最大數(shù)量可以認為無限大,超時時間60秒,并且是不緩存線程的SynchronousQueue隊列,所以可以來多少線程就去執(zhí)行多少線程,沒有任務(wù)了60秒之后就全部銷毀
ScheduledExecutorService
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
定時執(zhí)行線程任務(wù)的線程池:核心線程數(shù)由自己定義,最大線程數(shù)量為無限大,超時間為0秒,所以非核心線程閑置立刻被銷毀,用到了延遲隊列,可以設(shè)置線程延遲多少時間執(zhí)行,或者多長時間重復(fù)一次,比如:
scheduledExecutorService.schedule(() -> {}, 10, TimeUnit.SECONDS);
scheduledExecutorService.scheduleAtFixedRate(()->{}, 20, 10, TimeUnit.SECONDS);
第一表示10秒之后執(zhí)行線程,第二個表示20秒之后每10秒執(zhí)行一次線程
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看到核心線程數(shù)和最大線程數(shù)都是1,其實就是單線程了,所以也就不用考慮并發(fā)啊什么的了。
線程池的執(zhí)行過程
線程池執(zhí)行的過程也挺有意思:
- 提交一個線程,如果核心線程數(shù)量沒有滿的話,就啟動一個線程來執(zhí)行。
- 如果核心線程已滿,那么就插入到阻塞隊列里面去
- 如果阻塞隊列已經(jīng)滿的話,就啟動一個非核心線程
- 如果非核心線程也滿了,那就調(diào)用handler,報異?;蛘呓o替換掉了
原本我以為,如果核心線程已滿,就會立即啟動非核心線程來執(zhí)行線程,實際上是加到阻塞隊列里面,只有阻塞隊列滿了之后,才會啟動非核心線程??梢院唵蔚呐e個例子:
ThreadPoolExecutor service = new ThreadPoolExecutor(2, 10,
1000, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
service.execute(() -> {
try {
Thread.sleep(2000);
System.out.println(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(() -> {
try {
Thread.sleep(2000);
System.out.println(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(() -> {
System.out.println(3);
});
上面定義了一個2個核心線程數(shù)量和2個非核心線程數(shù)量的線程池,然后提交3個線程,前兩個沉睡2秒,結(jié)果總是:1 3 2
所以是等到核心線程執(zhí)行結(jié)束又執(zhí)行的第3個線程。
那么對于ScheduledExecutorService 這個線程池如果也是符合這個理論的話,豈不是設(shè)置的定時可能永遠不會執(zhí)行到了么? 我們做個試驗:
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
service.schedule(()->{
try {
Thread.sleep(3000);
System.out.println(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, TimeUnit.MILLISECONDS );
service.schedule(()->{
try {
Thread.sleep(3000);
System.out.println(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, TimeUnit.MILLISECONDS );
service.schedule(()-> System.out.println(3), 1000, TimeUnit.MILLISECONDS );
但是結(jié)果卻始終是 3 1 2. 所以對于這個定時的線程池來說這個理論并不符合
最后當應(yīng)用退出,別忘了關(guān)系線程池:
service.shutdown();
service.shutdownNow();
service.isShutdown();
其中isShutdown表示線程池關(guān)閉是否完成。
shutdown 表示中斷所有沒有執(zhí)行的線程任務(wù)
shutdownNow表示中斷執(zhí)行所有任務(wù)線程,包括正在執(zhí)行的線程,他會有一個返回值,返回的是那些沒有執(zhí)行的線程列表