線程池: 如果線程的數(shù)量很多,并且每個(gè)線程都是執(zhí)行一個(gè)時(shí)間很短的任務(wù)就結(jié)束,這樣頻繁創(chuàng)建線程會(huì)大大增加系統(tǒng)的開銷,因?yàn)閯?chuàng)建和消毀線程都需要資源和時(shí)間的
線程池有4種
1) newCachedThreadPool
新建一個(gè)可緩存線程,如果線程池長(zhǎng)度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程
2) newFixedThreadPool
創(chuàng)建一個(gè)固定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待
3) newScheduleThreadTool
創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行
4) newSingleThreadExecutor
創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO,LIFO)優(yōu)先級(jí)執(zhí)行
簡(jiǎn)單實(shí)例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TreadPoolDemo {
public static void main(String[] args) { //執(zhí)行速度
ExecutorService executorService1 = Executors.newCachedThreadPool(); //快
ExecutorService executorService2 = Executors.newFixedThreadPool(10); //中等
ExecutorService executorService3 = Executors.newSingleThreadExecutor(); //慢
for (int i = 0; i < 100; i++) {
executorService1.execute(new MyTask(i));
// executorService2.execute(new MyTask(i));
// executorService3.execute(new MyTask(i));
}
}
}
class MyTask implements Runnable{
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"一執(zhí)行第:"+i+"任務(wù)");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
分析:
-
newCachedThreadPool啟動(dòng)源碼:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
核心線程數(shù): 0
最大線程數(shù)為: Integer.MAX_VALUE
線程空閑回收時(shí)間為: 60秒
工作隊(duì)列為: SynchronousQueue (這個(gè)隊(duì)列只接受一個(gè)任務(wù),下面會(huì)講解到)newCachedThreadPool個(gè)人總結(jié) :
所以該線程池執(zhí)行的邏輯是,有多少個(gè)任務(wù)我就開啟多少個(gè)線程去運(yùn)行該任務(wù)(缺點(diǎn):造成性能的浪費(fèi),容易導(dǎo)致服務(wù)器性能崩潰.優(yōu)點(diǎn)是:執(zhí)行速度快) -
newFixedThreadPool啟動(dòng)源碼
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
核心線程數(shù): 10 (傳入的線程數(shù)代碼上是10)
最大線程數(shù)為: 10 (傳入的線程數(shù)代碼上是10)
線程空閑回收時(shí)間為: 0
工作隊(duì)列為: LinkedBlockingQueue (這個(gè)隊(duì)列能存儲(chǔ)多個(gè)任務(wù),下面會(huì)講解到)newFixedThreadPool個(gè)人總結(jié) :
線程可復(fù)用,執(zhí)行速度合理,不存在非核心線程(優(yōu)先: 線程復(fù)用性強(qiáng) 缺點(diǎn):初始化后不能修改線程數(shù)目,遇到高并發(fā)的情況也只能使用初始設(shè)定的線程去執(zhí)行任務(wù),導(dǎo)致用戶體驗(yàn)較差) -
newSingleThreadExecutor啟動(dòng)源碼
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
核心線程數(shù): 1
最大線程數(shù)為: 1
線程空閑回收時(shí)間為: 0
工作隊(duì)列為: LinkedBlockingQueue (這個(gè)隊(duì)列能存儲(chǔ)多個(gè)任務(wù),下面會(huì)講解到)newSingleThreadExecutor個(gè)人總結(jié) :
只有一個(gè)線程處理任務(wù)隊(duì)列(缺點(diǎn): 用戶體檢極差,處理能力極慢)
以上就是直接new一個(gè)線程池出來運(yùn)行,但是阿里開發(fā)手冊(cè)禁止使用該方法來啟動(dòng)一個(gè)線程池.而推薦使用ThreadPoolExecutor來創(chuàng)建
原因:
- 比如執(zhí)行newFixedThreadPool時(shí)會(huì)創(chuàng)建一個(gè)Integer.MAX_VALUE的線程隊(duì)列來存儲(chǔ)線程任務(wù),所有當(dāng)線程任務(wù)過多是(占用資源過多),會(huì)導(dǎo)致oom的問題
- 不能很好的配置核心線程數(shù)和最大線程數(shù),導(dǎo)致資源使用不合理,定義參數(shù)不合理
ThreadPoolExecutor講解
先看ThreadPoolExecutor源碼
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
構(gòu)造器提供了的參數(shù)我們來分析一下
-
int corePoolSize
線程池中核心線程數(shù)的最大值
-
int maximumPoolSize
線程池中能擁有最多線程數(shù)
-
long keepAliveTime
表示空閑線程的存活時(shí)間(線程回收時(shí)間)
-
TimeUnit unit
表示keepAliveTime的單位
-
BlockingQueue<Runnable> workQueue
用于緩存任務(wù)阻塞隊(duì)列
workQueue任務(wù)隊(duì)列:
對(duì)于不用的場(chǎng)景我們可能會(huì)采取不同的排隊(duì)策略,排隊(duì)的策略需要實(shí)現(xiàn)一個(gè)BlockQueue接口的任務(wù)等待隊(duì)列一下任務(wù)隊(duì)列分為兩類- 有限隊(duì)列
- SynchronousQueue: (一個(gè)不存元素的阻塞隊(duì)列)
每個(gè)插入操作必須等到另一個(gè)線程調(diào)用移除操作否則插入操作一值處于阻塞狀態(tài),吞吐量通常高于LinkedBlockQueue
2.ArrayBlockQueue: (有界阻塞隊(duì)列)
一個(gè)由數(shù)組支持的有界隊(duì)列,此隊(duì)列按FIFO(先進(jìn)先出)原則對(duì)元素進(jìn)行排序,隊(duì)列獲取從頭部開始獲得元素,從尾部出入到隊(duì)列.固定大小的數(shù)據(jù),一但創(chuàng)建了緩存區(qū),就不能再增加容量了. 向已滿的隊(duì)列里放入元素會(huì)導(dǎo)致操作受阻塞,向空的隊(duì)列中提取元素也會(huì)導(dǎo)致阻塞
- SynchronousQueue: (一個(gè)不存元素的阻塞隊(duì)列)
- 無限隊(duì)列
- LinkedBlockingQueue: (鏈表結(jié)構(gòu)的阻塞隊(duì)列)原理和ArrayBlockQueue隊(duì)列一樣,但是你可以指定容量,也可以不指定容量,不指定容量就是Integer.MAX_VALUE
- PriorityBlockIngQueue(一個(gè)具有優(yōu)先級(jí)的無限阻塞隊(duì)列)存放在PriorityBlockingQueue中的元素必須實(shí)現(xiàn)Comparable接口,這樣才能通過實(shí)現(xiàn)compareTo()方法進(jìn)行排序.優(yōu)先級(jí)最高的元素將始終排序到隊(duì)列的頭部,不會(huì)保證優(yōu)先級(jí)一樣的元素排序.
- LinkedBlockDeque和LinkedBlockingQueue一樣是基于鏈表的隊(duì)列,但是跟LinkedBlockingQueue不同的是,他兩頭都可以插入和取出元素
- LinkedTransferQueue 也是一個(gè)無限隊(duì)列伍宦,它除了具有一般隊(duì)列的操作特性外(先進(jìn)先出)勺届,還具有一個(gè)阻塞特性:LinkedTransferQueue可以由一對(duì)生產(chǎn)者/消費(fèi)者線程進(jìn)行操作些己,當(dāng)消費(fèi)者將一個(gè)新的元素插入隊(duì)列后,消費(fèi)者線程將會(huì)一直等待手素,直到某一個(gè)消費(fèi)者線程將這個(gè)元素取走,反之亦然胃榕。
- 有限隊(duì)列
- ThreadFactory threadFactory
指定創(chuàng)建線程的工廠
- RejectedExecutionHandler handler
表示當(dāng)workQueue隊(duì)列滿了,且池中的線程數(shù)到達(dá)maximumPoolSize時(shí),線程池采用的拒絕策略
策略 | 備注 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 拋出RejectedExecutionException異常 |
ThreadPoolExecutor.CallerRunsPolicy() | 由向線程池提交任務(wù)的線程來執(zhí)行該任務(wù) |
ThreadPoolExecutor.DiscardPolicy() | 拋棄當(dāng)前的任務(wù) |
ThreadPoolExecutor.DiscardOldestPolicy() | 拋棄最舊的任務(wù)(最先提交而沒有得到執(zhí)行的任務(wù)) |
阿里推薦我們使用ThreadPoolExecutor來創(chuàng)建線程池,原因是這樣創(chuàng)建線程比較靈活,可以根據(jù)自己的業(yè)務(wù)去定制線程池
ThreadPoolExecutor源碼這里就先不講解放到下篇文章講解,這里說說ThreadPoolExecutor的執(zhí)行過程,如何觸發(fā)maximumPoolSize的執(zhí)行線程啟動(dòng)
上例子:
import java.util.concurrent.*;
public class TreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
// 3個(gè)核心線程
3,
// 總共12個(gè)最多線程
12,
// 10秒空閑時(shí)間就回收
10,
TimeUnit.SECONDS,
//工作隊(duì)列大小
new ArrayBlockingQueue<>(50),
//策略為: 超出后丟棄
new ThreadPoolExecutor.DiscardPolicy());
int i = 0;
for (;;) {
i ++;
threadPoolExecutor.execute(new MyTask(i));
}
}
}
class MyTask implements Runnable{
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---執(zhí)行第:"+i+"任務(wù)");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
以上的執(zhí)行結(jié)果我拷貝一定出來分析:
pool-4-thread-1一執(zhí)行第:1任務(wù)
pool-4-thread-2一執(zhí)行第:2任務(wù)
pool-4-thread-3一執(zhí)行第:3任務(wù)
pool-4-thread-4一執(zhí)行第:54任務(wù)
pool-4-thread-5一執(zhí)行第:55任務(wù)
pool-4-thread-6一執(zhí)行第:56任務(wù)
pool-4-thread-7一執(zhí)行第:57任務(wù)
pool-4-thread-8一執(zhí)行第:58任務(wù)
pool-4-thread-9一執(zhí)行第:59任務(wù)
pool-4-thread-10一執(zhí)行第:60任務(wù)
pool-4-thread-11一執(zhí)行第:61任務(wù)
pool-4-thread-12一執(zhí)行第:62任務(wù)
pool-4-thread-3一執(zhí)行第:4任務(wù)
pool-4-thread-2一執(zhí)行第:6任務(wù)
pool-4-thread-1一執(zhí)行第:7任務(wù)
pool-4-thread-4一執(zhí)行第:5任務(wù)
pool-4-thread-8一執(zhí)行第:8任務(wù)
pool-4-thread-7一執(zhí)行第:9任務(wù)
pool-4-thread-6一執(zhí)行第:10任務(wù)
pool-4-thread-5一執(zhí)行第:11任務(wù)
pool-4-thread-12一執(zhí)行第:12任務(wù)
pool-4-thread-11一執(zhí)行第:13任務(wù)
pool-4-thread-10一執(zhí)行第:14任務(wù)
......
pool-4-thread-2一執(zhí)行第:29381082任務(wù)
pool-4-thread-4一執(zhí)行第:29381081任務(wù)
pool-4-thread-8一執(zhí)行第:29385303任務(wù)
...
看到執(zhí)行結(jié)果的朋友們都驚呆了,為什么這執(zhí)行順序1,2,3執(zhí)行的一下子跳到54個(gè)任務(wù),然后處理完62的任務(wù)又執(zhí)行4以下的任務(wù),接下來的順序就正常了
原因: 定義線程池是核心為3個(gè)線程,所以執(zhí)行了1-3任務(wù),沒問題!到后面線程pool-4-thread-(4-12)都是 maximumPoolSize - 核心線程 = 要?jiǎng)?chuàng)建的臨時(shí)線程數(shù).這里是 重點(diǎn) ,這些臨時(shí)線程是當(dāng)你工作隊(duì)列滿了的情況才創(chuàng)建出來的, 而工作隊(duì)列里面的任務(wù)是不先處理,而且讓工作隊(duì)列存放不下的任務(wù),直接交給臨時(shí)線程處理,所以臨時(shí)線程直接執(zhí)行了54-62任務(wù),然后處理完之后在去隊(duì)列里面從頭獲取任務(wù)執(zhí)行,所以執(zhí)行完62任務(wù)后后面的執(zhí)行順序就正常了.運(yùn)行到了下面,直接任務(wù)數(shù)到了29381082時(shí),這里我是啟動(dòng)了任務(wù)執(zhí)行處理不來是或工作隊(duì)列滿了時(shí),任務(wù)丟棄了,所以存到工作隊(duì)列任務(wù)的數(shù)字就是29381082,然后就通過線程去獲取任務(wù)指定任務(wù)