作用
一场勤、使用簡單
二们拙、方便管理多線程
三锡移、可重復利用線程
一個例子
通過下面這個例子,可以看出線程池使用起來非常簡單崔赌、方便
public class StartProvider {
public static void main(String[] args) {
ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(2);
for (int i = 1; i < 7; ++i) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("test");
}
});
}
}
}
Executors的newFixedThreadPool是創(chuàng)建一個大小固定意蛀,其它參數(shù)默認的線程池耸别,然后執(zhí)行Runnable任務,使用起來簡單县钥,下面是線程池的參數(shù):
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler)
- corePoolSize: 核心線程數(shù)(常駐線程池的線程數(shù))
- maximumPoolSize: 線程池中最大線程數(shù)目
- keepAliveTime: 池中線程數(shù)多于核心線程并且idle時秀姐,多于核心線程的其它線程存活時間
- unit: keepAliveTime時間單位
- workQueue: 存放任務的隊列(核心線程滿了之后,任務先進隊列)
- threadFactory: 創(chuàng)建線程的線程工廠
- handler: 線程池處理拋棄任務策略
參數(shù)設置
根據(jù)任務和運行的環(huán)境情況(cpu型若贮,io型省有,cpu核數(shù)等),設置參數(shù)的值谴麦,提高硬件的利用率
corePoolSize
核心線程數(shù)的大小蠢沿,在線程池初始化完成后,池中的線程個數(shù)為0匾效,每添加一個任務舷蟀,線程池就增加一個線程直到線程個數(shù)等于corePoolSize。對CPU繁忙型任務線程池大小(CPU核數(shù)為N)
線程池大小 = N + 1
對IO繁忙型任務線程池大小為
線程池大小 = (IO操作時間 + CPU運行時間)/CPU運行時間× N 或 2×N +1
maximumPoolSize
最大線程數(shù)弧轧,當workQueue已滿且線程池中的線程數(shù)少于maximumPoolSize時雪侥,如有新任務,就會創(chuàng)建新的線程精绎,一直到線程池中的線程大小為maximumPoolSize速缨。
keepAliveTime
idle線程(線程數(shù)大于corePoolSize且為idle)的存活時間,線程需要占用資源代乃,多余線程需要消亡旬牲,這些多余線程在將來很短時間內(nèi)也可能需要用到,keepAliveTime × unit是這些線程的存活時間搁吓。當任務數(shù)呈峰谷周期變化時原茅,keepAliveTime × unit設置為半周期,可以減少線程的創(chuàng)建和銷毀開銷堕仔。idle線程是存活keepAliveTime時間是通過取阻塞隊列里面的任務延時來實現(xiàn)的:
Runnable r = timed ?
workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS) : workQueue.take();
和keepAliveTime相對應的是allowCoreThreadTimeOut擂橘,表示線程池中的核心線程是否銷毀,線程池中的核心線程空閑較多時摩骨,通過設置allowCoreThreadTimeOut的值來表示是否銷毀idle的核心線程通贞。通過線程池的getActiveCount可以獲取線程池中正在執(zhí)行的線程數(shù)
workQueue
阻塞隊列,用于存儲任務恼五。常用的阻塞隊列有LinkedBlockingQueue昌罩、PriorityBlockingQueue和SynchronousQueue,它們分別用于不同的場景灾馒。LinkedBlockingQueue可以設置為有界阻塞隊列和無界阻塞隊列茎用,按先進先出的順序執(zhí)行任務;PriorityBlockingQueue優(yōu)先級隊列,根據(jù)Compatator比較器可以設置不同任務的執(zhí)行順序轨功;SynchronousQueue同步隊列旭斥,在插入一個任務進入隊列以后,需要取出這個任務才能繼續(xù)插入夯辖。
threadFactory
線程工廠琉预,繼承ThreadFactory可定制線程工廠
handler
任務隊列滿,且線程池中的線程數(shù)達到最大值(maximumPoolSize)蒿褂,有新任務到來時圆米,線程池用handler策略處理新任務。通過繼承RejectedExecutionHandler類可以定制處理策略啄栓,默認提供了三種處理策略:
- CallerRunsPolicy: 在當前線程中執(zhí)行任務
public class Main {
public static void main(String[] args){
final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 1; i <= 5; ++i) {
final int index = i;
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
if(!"main".equals(Thread.currentThread().getName())) {
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("nothing");
}
System.out.println("threadPoolInfo:poolSize = " + threadPoolExecutor.getPoolSize() + " queue size =" + threadPoolExecutor.getQueue().size() +
", currentTime:" + System.currentTimeMillis() + ", index = " + index + ", current thread name:" + Thread.currentThread().getName());
}
});
}
System.out.println("end");
}
}
執(zhí)行結(jié)果
線程池任務超出負載時娄帖,任務由線程池所在的線程執(zhí)行,例子中的為main線程
- AbortPolicy: 不執(zhí)行任務(默認策略)昙楚,并拋出RejectedExecutionException
public class Main {
public static void main(String[] args){
final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 1; i <= 12; ++i) {
final int index = i;
try {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("threadPoolInfo:poolSize = " + threadPoolExecutor.getPoolSize() + " queue size =" + threadPoolExecutor.getQueue().size() +
", currentTime:" + System.currentTimeMillis() + ", index = " + index + ", current thread name:" + Thread.currentThread().getName());
}
});
} catch (RejectedExecutionException e) {
System.out.println("message : " + e.getMessage());
try {
Thread.sleep(1000);
} catch(InterruptedException e1) {
System.out.println("interrupted");
}
}
}
System.out.println("end");
}
}
執(zhí)行結(jié)果
線程池任務超出負載時近速,沒有執(zhí)行任務,并拋出RejectExecutionException堪旧。拋出異常時削葱,需要捕獲異常,不然無法使用線程池淳梦。
- DiscardPolicy:不執(zhí)行任務析砸,不拋出異常(靜默策略)
- DiscardOldestPolicy : 拋棄阻塞隊列中最久的任務
一個問題
線程池是如何重復多次利用線程的?通過阻塞隊列爆袍,阻塞隊列中沒有任務時阻塞線程首繁,有任務則取出任務執(zhí)行,其原理和下面片段類似陨囊,但是復雜很多
public class Main {
public static void main(String[] args) {
final BlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<Runnable>();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Runnable runnable = blockingQueue.take();
runnable.run();
System.out.println("blocking........");
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
}
}).start();
blockingQueue.add(new Runnable() {
@Override
public void run() {
System.out.println("execute task!");
}
});
}
}
線程池中線程線程需要銷毀則調(diào)用阻塞隊列的poll方法弦疮,設置阻塞超時時間;否則調(diào)用take方法阻塞
關閉
shutdown和shutdownNow未關閉線程池的兩個操作蜘醋,shutdown關閉idle線程胁塞,線程池里面的任務繼續(xù)執(zhí)行;shutdownNow不等待線程執(zhí)行完畢压语,直接關閉線程闲先。