工作中,經(jīng)常會(huì)涉及到線程。比如有些任務(wù),經(jīng)常會(huì)交與線程去異步執(zhí)行。抑或服務(wù)端程序?yàn)槊總€(gè)請(qǐng)求單獨(dú)建立一個(gè)線程處理任務(wù)。線程之外的,比如我們用的數(shù)據(jù)庫(kù)連接。這些創(chuàng)建銷(xiāo)毀或者打開(kāi)關(guān)閉的操作,非常影響系統(tǒng)性能。所以,“池”的用處就凸顯出來(lái)了。
1. 為什么要使用線程池在3.6.1節(jié)介紹的實(shí)現(xiàn)方式中,對(duì)每個(gè)客戶都分配一個(gè)新的工作線程。當(dāng)工作線程與客戶通信結(jié)束,這個(gè)線程就被銷(xiāo)毀。這種實(shí)現(xiàn)方式有以下不足之處:
相對(duì)來(lái)說(shuō),使用線程池,會(huì)預(yù)創(chuàng)建一些線程,它們不斷的從工作隊(duì)列中取出任務(wù),然后執(zhí)行該任務(wù)。當(dāng)工作線程執(zhí)行完一個(gè)任務(wù)后,就會(huì)繼續(xù)執(zhí)行工作隊(duì)列中的另一個(gè)任務(wù)。優(yōu)點(diǎn)如下:
下面是自己寫(xiě)的一個(gè)簡(jiǎn)單的線程池,也是從Java網(wǎng)絡(luò)編程這本書(shū)上直接照著敲出來(lái)的
package thread;import java.util.LinkedList;/** * 線程池的實(shí)現(xiàn),根據(jù)常規(guī)線程池的長(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; //線程池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; } } } }}該線程池主要定義了一個(gè)工作隊(duì)列和一些預(yù)創(chuàng)建的線程。只要調(diào)用execute方法,就可以向線程提交任務(wù)。
后面線程在沒(méi)有任務(wù)的時(shí)候,會(huì)阻塞在getTask(),直到有新任務(wù)進(jìn)來(lái)被喚醒。
join和close都可以用來(lái)關(guān)閉線程池。不同的是,join會(huì)把隊(duì)列中的任務(wù)執(zhí)行完,而close則立刻清空隊(duì)列,并且中斷所有的工作線程。close()中的interrupt()相當(dāng)于調(diào)用了ThreadGroup中包含子線程的各自的interrupt(),所以有線程處于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ù)提供的線程池java提供了很好的線程池實(shí)現(xiàn),比我們自己的實(shí)現(xiàn)要更加健壯以及高效,同時(shí)功能也更加強(qiáng)大。
類(lèi)圖如下:

關(guān)于這類(lèi)線程池,前輩們已經(jīng)有很好的講解。任意百度下java線程池,都有寫(xiě)的非常詳細(xì)的例子和教程,這里就不再贅述。
java自帶線程池和隊(duì)列詳解
4. spring注入線程池在使用spring框架的時(shí)候,如果我們用java提供的方法來(lái)創(chuàng)建線程池,在多線程應(yīng)用中非常不方便管理,而且不符合我們使用spring的思想。(雖然spring可以通過(guò)靜態(tài)方法注入)
其實(shí),Spring本身也提供了很好的線程池的實(shí)現(xiàn)。這個(gè)類(lèi)叫做ThreadPoolTaskExecutor。
在spring中的配置如下:
<bean id="executorService" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="${threadpool.corePoolSize}" /> <!-- 線程池維護(hù)線程的最少數(shù)量 --> <property name="keepAliveSeconds" value="${threadpool.keepAliveSeconds}" /> <!-- 線程池維護(hù)線程所允許的空閑時(shí)間 --> <property name="maxPoolSize" value="${threadpool.maxPoolSize}" /> <!-- 線程池維護(hù)線程的最大數(shù)量 --> <property name="queueCapacity" value="${threadpool.queueCapacity}" /> <!-- 線程池所使用的緩沖隊(duì)列 --> </bean>5. 使用線程池的注意事項(xiàng)任何多線程程序都有死鎖的風(fēng)險(xiǎn),最簡(jiǎn)單的情形是兩個(gè)線程AB,A持有鎖1,請(qǐng)求鎖2,B持有鎖2,請(qǐng)求鎖1。(這種情況在MySQL的排他鎖也會(huì)出現(xiàn),不會(huì)數(shù)據(jù)庫(kù)會(huì)直接報(bào)錯(cuò)提示)。線程池中還有另一種死鎖:假設(shè)線程池中的所有工作線程都在執(zhí)行各自任務(wù)時(shí)被阻塞,它們?cè)诘却硞€(gè)任務(wù)A的執(zhí)行結(jié)果。而任務(wù)A卻處于隊(duì)列中,由于沒(méi)有空閑線程,一直無(wú)法得以執(zhí)行。這樣線程池的所有資源將一直阻塞下去,死鎖也就產(chǎn)生了。
如果線程池中的線程數(shù)目非常多,這些線程會(huì)消耗包括內(nèi)存和其他系統(tǒng)資源在內(nèi)的大量資源,從而嚴(yán)重影響系統(tǒng)性能。
線程池的工作隊(duì)列依靠wait()和notify()方法來(lái)使工作線程及時(shí)取得任務(wù),但這兩個(gè)方法難以使用。如果代碼錯(cuò)誤,可能會(huì)丟失通知,導(dǎo)致工作線程一直保持空閑的狀態(tài),無(wú)視工作隊(duì)列中需要處理的任務(wù)。因?yàn)樽詈檬褂靡恍┍容^成熟的線程池。
使用線程池的一個(gè)嚴(yán)重風(fēng)險(xiǎn)是線程泄漏。對(duì)于工作線程數(shù)目固定的線程池,如果工作線程在執(zhí)行任務(wù)時(shí)拋出RuntimeException或Error,并且這些異常或錯(cuò)誤沒(méi)有被捕獲,那么這個(gè)工作線程就異常終止,使線程池永久丟失了一個(gè)線程。(這一點(diǎn)太有意思)
另一種情況是,工作線程在執(zhí)行一個(gè)任務(wù)時(shí)被阻塞,如果等待用戶的輸入數(shù)據(jù),但是用戶一直不輸入數(shù)據(jù),導(dǎo)致這個(gè)線程一直被阻塞。這樣的工作線程名存實(shí)亡,它實(shí)際上不執(zhí)行任何任務(wù)了。如果線程池中的所有線程都處于這樣的狀態(tài),那么線程池就無(wú)法加入新的任務(wù)了。
當(dāng)工作線程隊(duì)列中有大量排隊(duì)等待執(zhí)行的任務(wù)時(shí),這些任務(wù)本身可能會(huì)消耗太多的系統(tǒng)資源和引起資源缺乏。
綜上所述,使用線程池時(shí),要遵循以下原則:
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注