Java線(xiàn)程池的幾種實(shí)現(xiàn)方法及常見(jiàn)問(wèn)題解答
工作中,經(jīng)常會(huì)涉及到線(xiàn)程。比如有些任務(wù),經(jīng)常會(huì)交與線(xiàn)程去異步執(zhí)行。抑或服務(wù)端程序?yàn)槊總€(gè)請(qǐng)求單獨(dú)建立一個(gè)線(xiàn)程處理任務(wù)。線(xiàn)程之外的,比如我們用的數(shù)據(jù)庫(kù)連接。這些創(chuàng)建銷(xiāo)毀或者打開(kāi)關(guān)閉的操作,非常影響系統(tǒng)性能。所以,“池”的用處就凸顯出來(lái)了。
1. 為什么要使用線(xiàn)程池
在3.6.1節(jié)介紹的實(shí)現(xiàn)方式中,對(duì)每個(gè)客戶(hù)都分配一個(gè)新的工作線(xiàn)程。當(dāng)工作線(xiàn)程與客戶(hù)通信結(jié)束,這個(gè)線(xiàn)程就被銷(xiāo)毀。這種實(shí)現(xiàn)方式有以下不足之處:
•服務(wù)器創(chuàng)建和銷(xiāo)毀工作的開(kāi)銷(xiāo)( 包括所花費(fèi)的時(shí)間和系統(tǒng)資源 )很大。這一項(xiàng)不用解釋?zhuān)梢匀ゲ橄?線(xiàn)程創(chuàng)建過(guò)程"。除了機(jī)器本身所做的工作,我們還要實(shí)例化,啟動(dòng),這些都需要占用堆棧資源。
•除了創(chuàng)建和銷(xiāo)毀線(xiàn)程的開(kāi)銷(xiāo)之外,活動(dòng)的線(xiàn)程也消耗系統(tǒng)資源。 這個(gè)應(yīng)該是對(duì)堆棧資源的消耗,猜測(cè)數(shù)據(jù)庫(kù)連接數(shù)設(shè)置一個(gè)合理的值,也有這個(gè)考慮。
•如果線(xiàn)程數(shù)目固定,并且每個(gè)線(xiàn)程都有很長(zhǎng)的聲明周期,那么線(xiàn)程切換也是相對(duì)固定的。不同的操作系統(tǒng)有不同的切換周期,一般20ms左右。這里說(shuō)的切換是在jvm以及底層操作系統(tǒng)的調(diào)度下,線(xiàn)程之間轉(zhuǎn)讓cpu的使用權(quán)。如果頻繁創(chuàng)建和銷(xiāo)毀線(xiàn)程,那么就將頻繁的切換線(xiàn)程,因?yàn)橐粋€(gè)線(xiàn)程銷(xiāo)毀后,必然要讓出使用權(quán)給已經(jīng)就緒的線(xiàn)程,使該線(xiàn)程獲得運(yùn)行機(jī)會(huì)。在這種情況下,線(xiàn)程之間的切換就不在遵循系統(tǒng)的固定切換周期,切換線(xiàn)程的開(kāi)銷(xiāo)甚至比創(chuàng)建和銷(xiāo)毀的開(kāi)銷(xiāo)還要大。
相對(duì)來(lái)說(shuō),使用線(xiàn)程池,會(huì)預(yù)創(chuàng)建一些線(xiàn)程,它們不斷的從工作隊(duì)列中取出任務(wù),然后執(zhí)行該任務(wù)。當(dāng)工作線(xiàn)程執(zhí)行完一個(gè)任務(wù)后,就會(huì)繼續(xù)執(zhí)行工作隊(duì)列中的另一個(gè)任務(wù)。優(yōu)點(diǎn)如下:
•減少了創(chuàng)建和銷(xiāo)毀的次數(shù),每個(gè)工作線(xiàn)程都可以一直被重用,能執(zhí)行多個(gè)任務(wù)。
•可以根據(jù)系統(tǒng)的承載能力,方便的調(diào)整線(xiàn)程池中線(xiàn)程的數(shù)目,防止因?yàn)橄倪^(guò)量的系統(tǒng)資源而導(dǎo)致系統(tǒng)崩潰。
2. 線(xiàn)程池的簡(jiǎn)單實(shí)現(xiàn)
下面是自己寫(xiě)的一個(gè)簡(jiǎn)單的線(xiàn)程池,也是從Java網(wǎng)絡(luò)編程這本書(shū)上直接照著敲出來(lái)的
package thread;
import java.util.LinkedList;
/**
* 線(xiàn)程池的實(shí)現(xiàn),根據(jù)常規(guī)線(xiàn)程池的長(zhǎng)度,最大長(zhǎng)度,隊(duì)列長(zhǎng)度,我們可以增加數(shù)目限制實(shí)現(xiàn)
* @author Han
*/
public class MyThreadPool extends ThreadGroup{
//cpu 數(shù)量 ---Runtime.getRuntime().availableProcessors();
//是否關(guān)閉
private boolean isClosed = false;
//隊(duì)列
private LinkedList<Runnable> workQueue;
//線(xiàn)程池id
private static int threadPoolID;
private int threadID;
public MyThreadPool(int poolSize){
super("MyThreadPool."+threadPoolID);
threadPoolID++;
setDaemon(true);
workQueue = new LinkedList<Runnable>();
for(int i = 0;i<poolSize;i++){
new WorkThread().start();
}
}
//這里可以換成ConcurrentLinkedQueue,就可以避免使用synchronized的效率問(wèn)題
public synchronized void execute(Runnable task){
if(isClosed){
throw new IllegalStateException("連接池已經(jīng)關(guān)閉...");
}else{
workQueue.add(task);
notify();
}
}
protected synchronized Runnable getTask() throws InterruptedException {
while(workQueue.size() == 0){
if(isClosed){
return null;
}
wait();
}
return workQueue.removeFirst();
}
public synchronized void close(){
if(!isClosed){
isClosed = true;
workQueue.clear();
interrupt();
}
}
public void join(){
synchronized (this) {
isClosed = true;
notifyAll();
}
Thread[] threads = new Thread[activeCount()];
int count = enumerate(threads);
for(int i = 0;i<count;i++){
try {
threads[i].join();
} catch (Exception e) {
}
}
}
class WorkThread extends Thread{
public WorkThread(){
super(MyThreadPool.this,"workThread"+(threadID++));
System.out.println("create...");
}
@Override
public void run() {
while(!isInterrupted()){
System.out.println("run..");
Runnable task = null;
try {
//這是一個(gè)阻塞方法
task = getTask();
} catch (Exception e) {
}
if(task != null){
task.run();
}else{
break;
}
}
}
}
}
該線(xiàn)程池主要定義了一個(gè)工作隊(duì)列和一些預(yù)創(chuàng)建的線(xiàn)程。只要調(diào)用execute方法,就可以向線(xiàn)程提交任務(wù)。
后面線(xiàn)程在沒(méi)有任務(wù)的時(shí)候,會(huì)阻塞在getTask(),直到有新任務(wù)進(jìn)來(lái)被喚醒。
join和close都可以用來(lái)關(guān)閉線(xiàn)程池。不同的是,join會(huì)把隊(duì)列中的任務(wù)執(zhí)行完,而close則立刻清空隊(duì)列,并且中斷所有的工作線(xiàn)程。close()中的interrupt()相當(dāng)于調(diào)用了ThreadGroup中包含子線(xiàn)程的各自的interrupt(),所以有線(xiàn)程處于wait或者sleep時(shí),都會(huì)拋出InterruptException
測(cè)試類(lèi)如下:
public class TestMyThreadPool {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool = new MyThreadPool(3);
for(int i = 0;i<10;i++){
pool.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("working...");
}
});
}
pool.join();
//pool.close();
}
}
3. jdk類(lèi)庫(kù)提供的線(xiàn)程池
java提供了很好的線(xiàn)程池實(shí)現(xiàn),比我們自己的實(shí)現(xiàn)要更加健壯以及高效,同時(shí)功能也更加強(qiáng)大。
類(lèi)圖如下:

關(guān)于這類(lèi)線(xiàn)程池,前輩們已經(jīng)有很好的講解。任意百度下java線(xiàn)程池,都有寫(xiě)的非常詳細(xì)的例子和教程,這里就不再贅述。
4. spring注入線(xiàn)程池
在使用spring框架的時(shí)候,如果我們用java提供的方法來(lái)創(chuàng)建線(xiàn)程池,在多線(xiàn)程應(yīng)用中非常不方便管理,而且不符合我們使用spring的思想。(雖然spring可以通過(guò)靜態(tài)方法注入)
其實(shí),Spring本身也提供了很好的線(xiàn)程池的實(shí)現(xiàn)。這個(gè)類(lèi)叫做ThreadPoolTaskExecutor。
在spring中的配置如下:
<bean id="executorService" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="${threadpool.corePoolSize}" />
<!-- 線(xiàn)程池維護(hù)線(xiàn)程的最少數(shù)量 -->
<property name="keepAliveSeconds" value="${threadpool.keepAliveSeconds}" />
<!-- 線(xiàn)程池維護(hù)線(xiàn)程所允許的空閑時(shí)間 -->
<property name="maxPoolSize" value="${threadpool.maxPoolSize}" />
<!-- 線(xiàn)程池維護(hù)線(xiàn)程的最大數(shù)量 -->
<property name="queueCapacity" value="${threadpool.queueCapacity}" />
<!-- 線(xiàn)程池所使用的緩沖隊(duì)列 -->
</bean>
5. 使用線(xiàn)程池的注意事項(xiàng)
•死鎖
任何多線(xiàn)程程序都有死鎖的風(fēng)險(xiǎn),最簡(jiǎn)單的情形是兩個(gè)線(xiàn)程AB,A持有鎖1,請(qǐng)求鎖2,B持有鎖2,請(qǐng)求鎖1。(這種情況在mysql的排他鎖也會(huì)出現(xiàn),不會(huì)數(shù)據(jù)庫(kù)會(huì)直接報(bào)錯(cuò)提示)。線(xiàn)程池中還有另一種死鎖:假設(shè)線(xiàn)程池中的所有工作線(xiàn)程都在執(zhí)行各自任務(wù)時(shí)被阻塞,它們?cè)诘却硞€(gè)任務(wù)A的執(zhí)行結(jié)果。而任務(wù)A卻處于隊(duì)列中,由于沒(méi)有空閑線(xiàn)程,一直無(wú)法得以執(zhí)行。這樣線(xiàn)程池的所有資源將一直阻塞下去,死鎖也就產(chǎn)生了。
•系統(tǒng)資源不足
如果線(xiàn)程池中的線(xiàn)程數(shù)目非常多,這些線(xiàn)程會(huì)消耗包括內(nèi)存和其他系統(tǒng)資源在內(nèi)的大量資源,從而嚴(yán)重影響系統(tǒng)性能。
•并發(fā)錯(cuò)誤
線(xiàn)程池的工作隊(duì)列依靠wait()和notify()方法來(lái)使工作線(xiàn)程及時(shí)取得任務(wù),但這兩個(gè)方法難以使用。如果代碼錯(cuò)誤,可能會(huì)丟失通知,導(dǎo)致工作線(xiàn)程一直保持空閑的狀態(tài),無(wú)視工作隊(duì)列中需要處理的任務(wù)。因?yàn)樽詈檬褂靡恍┍容^成熟的線(xiàn)程池。
•線(xiàn)程泄漏
使用線(xiàn)程池的一個(gè)嚴(yán)重風(fēng)險(xiǎn)是線(xiàn)程泄漏。對(duì)于工作線(xiàn)程數(shù)目固定的線(xiàn)程池,如果工作線(xiàn)程在執(zhí)行任務(wù)時(shí)拋出RuntimeException或Error,并且這些異?;蝈e(cuò)誤沒(méi)有被捕獲,那么這個(gè)工作線(xiàn)程就異常終止,使線(xiàn)程池永久丟失了一個(gè)線(xiàn)程。(這一點(diǎn)太有意思)
另一種情況是,工作線(xiàn)程在執(zhí)行一個(gè)任務(wù)時(shí)被阻塞,如果等待用戶(hù)的輸入數(shù)據(jù),但是用戶(hù)一直不輸入數(shù)據(jù),導(dǎo)致這個(gè)線(xiàn)程一直被阻塞。這樣的工作線(xiàn)程名存實(shí)亡,它實(shí)際上不執(zhí)行任何任務(wù)了。如果線(xiàn)程池中的所有線(xiàn)程都處于這樣的狀態(tài),那么線(xiàn)程池就無(wú)法加入新的任務(wù)了。
•任務(wù)過(guò)載
當(dāng)工作線(xiàn)程隊(duì)列中有大量排隊(duì)等待執(zhí)行的任務(wù)時(shí),這些任務(wù)本身可能會(huì)消耗太多的系統(tǒng)資源和引起資源缺乏。
綜上所述,使用線(xiàn)程池時(shí),要遵循以下原則:
1. 如果任務(wù)A在執(zhí)行過(guò)程中需要同步等待任務(wù)B的執(zhí)行結(jié)果,那么任務(wù)A不適合加入到線(xiàn)程池的工作隊(duì)列中。如果把像任務(wù)A一樣的需要等待其他任務(wù)執(zhí)行結(jié)果的加入到隊(duì)列中,可能造成死鎖
2. 如果執(zhí)行某個(gè)任務(wù)時(shí)可能會(huì)阻塞,并且是長(zhǎng)時(shí)間的阻塞,則應(yīng)該設(shè)定超時(shí)時(shí)間,避免工作線(xiàn)程永久的阻塞下去而導(dǎo)致線(xiàn)程泄漏。在服務(wù)器才程序中,當(dāng)線(xiàn)程等待客戶(hù)連接,或者等待客戶(hù)發(fā)送的數(shù)據(jù)時(shí),都可能造成阻塞,可以通過(guò)以下方式設(shè)置時(shí)間:
調(diào)用ServerSocket的setSotimeout方法,設(shè)定等待客戶(hù)連接的超時(shí)時(shí)間。
對(duì)于每個(gè)與客戶(hù)連接的socket,調(diào)用該socket的setSoTImeout方法,設(shè)定等待客戶(hù)發(fā)送數(shù)據(jù)的超時(shí)時(shí)間。
3. 了解任務(wù)的特點(diǎn),分析任務(wù)是執(zhí)行經(jīng)常會(huì)阻塞io操作,還是執(zhí)行一直不會(huì)阻塞的運(yùn)算操作。前者時(shí)斷時(shí)續(xù)的占用cpu,而后者具有更高的利用率。預(yù)計(jì)完成任務(wù)大概需要多長(zhǎng)時(shí)間,是短時(shí)間任務(wù)還是長(zhǎng)時(shí)間任務(wù),然后根據(jù)任務(wù)的特點(diǎn),對(duì)任務(wù)進(jìn)行分類(lèi),然后把不同類(lèi)型的任務(wù)加入到不同的線(xiàn)程池的工作隊(duì)列中,這樣就可以根據(jù)任務(wù)的特點(diǎn),分配調(diào)整每個(gè)線(xiàn)程池
4. 調(diào)整線(xiàn)程池的大小。線(xiàn)程池的最佳大小主要取決于系統(tǒng)的可用cpu的數(shù)目,以及工作隊(duì)列中任務(wù)的特點(diǎn)。假如一個(gè)具有N個(gè)cpu的系統(tǒng)上只有一個(gè)工作隊(duì)列,并且其中全部是運(yùn)算性質(zhì)(不會(huì)阻塞)的任務(wù),那么當(dāng)線(xiàn)程池?fù)碛蠳或N+1個(gè)工作線(xiàn)程時(shí),一般會(huì)獲得最大的cpu使用率。
如果工作隊(duì)列中包含會(huì)執(zhí)行IO操作并經(jīng)常阻塞的任務(wù),則要讓線(xiàn)程池的大小超過(guò)可用 cpu的數(shù)量,因?yàn)椴⒉皇撬械墓ぷ骶€(xiàn)程都一直在工作。選擇一個(gè)典型的任務(wù),然后估計(jì)在執(zhí)行這個(gè)任務(wù)的工程中,等待時(shí)間與實(shí)際占用cpu進(jìn)行運(yùn)算的時(shí)間的比例WT/ST。對(duì)于一個(gè)具有N個(gè)cpu的系統(tǒng),需要設(shè)置大約N*(1+WT/ST)個(gè)線(xiàn)程來(lái)保證cpu得到充分利用。
當(dāng)然,cpu利用率不是調(diào)整線(xiàn)程池過(guò)程中唯一要考慮的事項(xiàng),隨著線(xiàn)程池工作數(shù)目的增長(zhǎng),還會(huì)碰到內(nèi)存或者其他資源的限制,如套接字,打開(kāi)的文件句柄或數(shù)據(jù)庫(kù)連接數(shù)目等。要保證多線(xiàn)程消耗的系統(tǒng)資源在系統(tǒng)承受的范圍之內(nèi)。
5. 避免任務(wù)過(guò)載。服務(wù)器應(yīng)根據(jù)系統(tǒng)的承載能力,限制客戶(hù)并發(fā)連接的數(shù)目。當(dāng)客戶(hù)的連接超過(guò)了限制值,服務(wù)器可以拒絕連接,并進(jìn)行友好提示,或者限制隊(duì)列長(zhǎng)度。
以上這篇Java線(xiàn)程池的幾種實(shí)現(xiàn)方法及常見(jiàn)問(wèn)題解答就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- java線(xiàn)程池使用后到底要關(guān)閉嗎
- java中通用的線(xiàn)程池實(shí)例代碼
- Java 線(xiàn)程池詳解及實(shí)例代碼
- Java實(shí)現(xiàn)終止線(xiàn)程池中正在運(yùn)行的定時(shí)任務(wù)
- 詳談Java幾種線(xiàn)程池類(lèi)型介紹及使用方法
- 四種Java線(xiàn)程池用法解析
- Java中四種線(xiàn)程池的使用示例詳解
- java簡(jiǎn)單實(shí)現(xiàn)多線(xiàn)程及線(xiàn)程池實(shí)例詳解
- 深入java線(xiàn)程池的使用詳解
- Java線(xiàn)程池配置的一些常見(jiàn)誤區(qū)總結(jié)
相關(guān)文章
解決java.util.NoSuchElementException異常正確方法
java.util.NoSuchElementException是Java中的一種異常,表示在迭代器或枚舉中找不到元素,這篇文章主要給大家介紹了關(guān)于解決java.util.NoSuchElementException異常的相關(guān)資料,需要的朋友可以參考下2023-11-11
SpringCloud整合分布式服務(wù)跟蹤zipkin的實(shí)現(xiàn)
這篇文章主要介紹了SpringCloud整合分布式服務(wù)跟蹤zipkin的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
Mac下安裝配置Maven并在IDEA中配置的詳細(xì)教程
這篇文章主要介紹了Mac下安裝配置Maven并在IDEA中配置,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
Java+mysql本地圖片上傳數(shù)據(jù)庫(kù)及下載示例
本篇文章主要介紹了Java+mysql本地圖片上傳數(shù)據(jù)庫(kù)及下載示例,具有一定的參加價(jià)值,有興趣的可以了解一下。2017-01-01

