一云芦、為什么要使用線程池
構(gòu)建服務(wù)器應(yīng)用的簡單模型是:每當(dāng)一個請求到達(dá)就創(chuàng)建一個新線程贪惹,在新線程中請求服務(wù)察蹲。在原型開發(fā)這種方法工作得很好如果部署以這種方式運行的服務(wù)器應(yīng)用程序济锄,這種方法存在嚴(yán)重不足之一是:服務(wù)器應(yīng)用程序中單任務(wù)處理的時間短聊浅,請求數(shù)大餐抢,而每當(dāng)有一個新請求就為其創(chuàng)建一個新線程,請求處理結(jié)束后還要負(fù)責(zé)銷毀線程低匙,這大大增加了系統(tǒng)在創(chuàng)建和銷毀線程上時間的花費旷痕,還消耗了系統(tǒng)資源,往往比處理用戶實際請求的時間和資源更多顽冶。
線程池為線程生命開銷問題和資源不足問題提供了解決方案欺抗。多個任務(wù)重用線程,線程創(chuàng)建的開銷被分配到了多個任務(wù)上强重。當(dāng)請求到達(dá)時線程已經(jīng)存在绞呈,消除了線程創(chuàng)建帶來的延遲。而且通過適當(dāng)調(diào)整線程池中線程數(shù)量间景,當(dāng)請求超過閾值時佃声,強制其他新到的請求等待,直到獲得一個線程來處理倘要,防止資源不足
二圾亏、線程池的種類及區(qū)別
ThreadPoolExecutor類屬性
public ThreadPoolExecutor(int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6, ThreadFactory var7, RejectedExecutionHandler var8) {
this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
this.mainLock = new ReentrantLock();
this.workers = new HashSet();
this.termination = this.mainLock.newCondition();
if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
if (var6 != null && var7 != null && var8 != null) {
this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
this.corePoolSize = var1;
this.maximumPoolSize = var2;
this.workQueue = var6;
this.keepAliveTime = var5.toNanos(var3);
this.threadFactory = var7;
this.handler = var8;
} else {
throw new NullPointerException();
}
} else {
throw new IllegalArgumentException();
}
}
corePoolSize:核心線程數(shù);
maximumPoolSize:最大線程數(shù)封拧,線程中中允許存在的最大線程數(shù)志鹃;
keepAliveTime:線程存活時間,超過核心線程數(shù)的線程泽西,當(dāng)線程處理空閑狀態(tài)下弄跌,且維持時間達(dá)到keepAliveTime時,線程被銷毀尝苇;
unit:keepAliveTime的時間單位
workQuene:工作隊列铛只,用于存放待執(zhí)行的線程任務(wù)
threadFactory:創(chuàng)建線程的工廠,用于標(biāo)記區(qū)分不同線程池創(chuàng)建出來的線程糠溜;
handler:當(dāng)達(dá)到線程數(shù)上限或工作隊列已滿時的處理邏輯淳玩;
線程執(zhí)行策略
如果運行線程少于corePoolSize,則會創(chuàng)建一個新線程處理請求非竿,不將其添加到隊列中
如果線程數(shù)達(dá)到corePoolSize則將新的請求放入隊列中蜕着,若請求無法加入隊列,且線程數(shù)未達(dá)到maximumPoolSize則創(chuàng)建新線程,否則任務(wù)將被拒絕承匣。
BlockingQueue類型
無界隊列
隊列大小無限制蓖乘,常用的為無界的LinkedBlockingQueue,使用該隊列做為阻塞隊列時要尤其當(dāng)心韧骗,當(dāng)任務(wù)耗時較長時可能會導(dǎo)致大量新任務(wù)在隊列中堆積最終導(dǎo)致OOM嘉抒。最近工作中就遇到因為采用LinkedBlockingQueue作為阻塞隊列,部分任務(wù)耗時80s+且不停有新任務(wù)進來袍暴,導(dǎo)致cpu和內(nèi)存飆升服務(wù)器掛掉些侍。
有界隊列
常用的有兩類,一類是遵循FIFO原則的隊列如ArrayBlockingQueue與有界的LinkedBlockingQueue政模,另一類是優(yōu)先級隊列如PriorityBlockingQueue岗宣。PriorityBlockingQueue中的優(yōu)先級由任務(wù)的Comparator決定。
使用有界隊列時隊列大小需和線程池大小互相配合淋样,線程池較小有界隊列較大時可減少內(nèi)存消耗耗式,降低cpu使用率和上下文切換,但是可能會限制系統(tǒng)吞吐量趁猴。
同步移交
如果不希望任務(wù)在隊列中等待而是希望將任務(wù)直接移交給工作線程刊咳,可使用SynchronousQueue作為等待隊列。SynchronousQueue不是一個真正的隊列躲叼,而是一種線程之間移交的機制。要將一個元素放入SynchronousQueue中企巢,必須有另一個線程正在等待接收這個元素枫慷。只有在使用無界線程池或者有飽和策略時才建議使用該隊列。
1.固定大小線程池newFixedThreadPool
public class Main {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
Thread t1 = new Thread(new MyThread(1));
Thread t2 = new Thread(new MyThread(2));
Thread t3 = new Thread(new MyThread(3));
Thread t4 = new Thread(new MyThread(4));
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.shutdown();
}
}
class MyThread implements Runnable{
int count;
MyThread(int i) {
this.count = i;
}
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("ThreadName = " + count + "----");
}
}
}
執(zhí)行結(jié)果:
將Executors.newFixedThreadPool(2)改成Executors.newFixedThreadPool(5)后執(zhí)行結(jié)果:
可以看出該方法指定運行線程最大數(shù)目浪规,超過這個數(shù)目線程加進去不會執(zhí)行或听,且賢臣運行順序不受加入順序影響。
2.單任務(wù)線程池笋婿,newSingleThreadExecutor
僅僅是把上述代碼中的ExecutorService pool = Executors.newFixedThreadPool(2)改為ExecutorService pool = Executors.newSingleThreadExecutor();
輸出結(jié)果:
可以看出該線程池按線程加入順序執(zhí)行誉裆,哪怕當(dāng)前線程休眠也不會跳到下一個線程執(zhí)行
3.可變尺寸線程池,newCachedThreadPool
與上面的類似缸濒,只是改動下pool的創(chuàng)建方式:ExecutorService pool = Executors.newCachedThreadPool();
執(zhí)行結(jié)果:
可根據(jù)需要創(chuàng)建新線程足丢,當(dāng)線程阻塞時會跳到其他線程執(zhí)行
三、線程池使用注意
謹(jǐn)慎使用Executors創(chuàng)建線程
Executors是java并發(fā)包提供的庇配,用于快速創(chuàng)建不同類型線程池斩跌。
Executors包中部分源碼:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
public static ExecutorService newCachedThreadPool(ThreadFactory var0) {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), var0);
}
使用Executors創(chuàng)建線程池時代碼:
ExecutorService pool = Executors.newCachedThreadPool();
Thread t1 = new Thread(new MyThread(1));
Thread t2 = new Thread(new MyThread(2));
Thread t3 = new Thread(new MyThread(3));
Thread t4 = new Thread(new MyThread(4));
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.shutdown();
這種方法在開發(fā)個人或臨時項目時速度很快,幾行代碼就解決捞慌,但是在大型項目中是禁止使用的耀鸦。
通過上面的源碼可以直觀地看到,它是自動地為ThreadPoolExecutor指定參數(shù)啸澡,Executors創(chuàng)建線程池時袖订,使用的是無邊界隊列SynchronousQueue氮帐,不斷加入任務(wù)會出現(xiàn)內(nèi)存溢出問題。