【JAVA】線程池面試總結(jié)

線程的幾種狀態(tài)

其狀態(tài)總共有 5 種
1.初始狀態(tài):線程已被創(chuàng)建,但是還不被允許分配CPU執(zhí)行。注意,這個(gè)被創(chuàng)建其實(shí)是屬于編程語(yǔ)言層面的,實(shí)際在操作系統(tǒng)里,真正的線程還沒(méi)被創(chuàng)建, 比如 Java 語(yǔ)言中的 new Thread()。
2.可運(yùn)行狀態(tài):線程可以分配CPU執(zhí)行,這時(shí),操作系統(tǒng)中線程已經(jīng)被創(chuàng)建成功了
3.運(yùn)行狀態(tài):操作系統(tǒng)會(huì)為處在可運(yùn)行狀態(tài)的線程分配CPU時(shí)間片,被 CPU 臨幸后,處在可運(yùn)行狀態(tài)的線程就會(huì)變?yōu)檫\(yùn)行狀態(tài)
4.休眠狀態(tài):如果處在運(yùn)行狀態(tài)的線程調(diào)用某個(gè)阻塞的API或等待某個(gè)事件條件可用,那么線程就會(huì)轉(zhuǎn)換到休眠狀態(tài),注意:此時(shí)線程會(huì)釋放CPU使用權(quán),休眠的線程永遠(yuǎn)沒(méi)有機(jī)會(huì)獲得CPU使用權(quán),只有當(dāng)?shù)却录霈F(xiàn)后,線程會(huì)從休眠狀態(tài)轉(zhuǎn)換到可運(yùn)行狀態(tài)
5.終止?fàn)顟B(tài):線程執(zhí)行完或者出現(xiàn)異常 (被interrupt那種不算的哈,后續(xù)會(huì)說(shuō))就會(huì)進(jìn)入終止?fàn)顟B(tài),正式走到生命的盡頭,沒(méi)有起死回生的機(jī)會(huì)。

線程生命周期

在 Thread 的源碼中,定義了一個(gè)枚舉類 State,里面清晰明了的寫(xiě)了Java語(yǔ)言中線程的6種狀態(tài):
1.NEW
2.RUNNABLE
3.BLOCKED
4.WAITING
5.TIMED_WAITING
6.TERMINATED

Java 語(yǔ)言中
將通用線程狀態(tài)的可運(yùn)行狀態(tài)和運(yùn)行狀態(tài)合并為 Runnable,將休眠狀態(tài)細(xì)分為三種 (BLOCKED/WAITING/TIMED_WAITING); 反過(guò)來(lái)理解這句話,就是這三種狀態(tài)在操作系統(tǒng)的眼中都是休眠狀態(tài),同樣不會(huì)獲得CPU使用權(quán),進(jìn)一步簡(jiǎn)潔的說(shuō),除去線程生死,我們只要玩轉(zhuǎn) RUNNABLE 和休眠狀態(tài)的轉(zhuǎn)換就可以了,編寫(xiě)并發(fā)程序也多數(shù)是這兩種狀態(tài)的轉(zhuǎn)換。所以我們需要了解,有哪些時(shí)機(jī),會(huì)觸發(fā)這些狀態(tài)轉(zhuǎn)換

如何查看線程處在什么狀態(tài)

Thread 類中同樣存在 getState() 方法用于查看當(dāng)前線程狀態(tài),該方法就是返回上面提到的枚舉類 State

通過(guò)繼承 Thread 或?qū)崿F(xiàn) Runnable 接口定義線程后,這時(shí)的狀態(tài)都是 NEW

Thread thread = new Thread(() -> {});
System.out.println(thread.getState());

調(diào)用了 start() 方法之后,線程就處在 RUNNABLE 狀態(tài)了

Thread thread = new Thread(() -> {});
thread.start();
//Thread.sleep(1000);
System.out.println(thread.getState());

等待 synchronized 內(nèi)置鎖,就會(huì)處在 BLOCKED 狀態(tài)
調(diào)用線程的 join() 等方法,從 RUNNABLE 變?yōu)?WAITING 狀態(tài)
調(diào)用了 sleep(long) 等方法,線程從 RUNNABLE 變?yōu)?TIMED-WAITING 狀態(tài)
線程執(zhí)行完自然就到了 TERMINATED 狀態(tài)了

原文地址

創(chuàng)建一個(gè)線程的過(guò)程

1.為一個(gè)線程棧分配內(nèi)存,該棧為每個(gè)線程方法調(diào)用保存一個(gè)棧幀
2.每一棧幀由一個(gè)局部變量數(shù)組、返回值、操作數(shù)堆棧和常量池組成
3.一些支持本機(jī)方法的 jvm 也會(huì)分配一個(gè)本機(jī)堆棧
4.每個(gè)線程獲得一個(gè)程序計(jì)數(shù)器,告訴它當(dāng)前處理器執(zhí)行的指令是什么
5.系統(tǒng)創(chuàng)建一個(gè)與Java線程對(duì)應(yīng)的本機(jī)線程
6.將與線程相關(guān)的描述符添加到JVM內(nèi)部數(shù)據(jù)結(jié)構(gòu)中
7.線程共享堆和方法區(qū)域

為什么要用線程池

手動(dòng)創(chuàng)建線程的缺點(diǎn):
1.不受控風(fēng)險(xiǎn)
系統(tǒng)資源有限,每個(gè)人針對(duì)不同業(yè)務(wù)都可以手動(dòng)創(chuàng)建線程,并且創(chuàng)建標(biāo)準(zhǔn)不一樣(比如線程沒(méi)有名字)。當(dāng)系統(tǒng)運(yùn)行起來(lái),所有線程都在瘋狂搶占資源,內(nèi)存被無(wú)情榨干耗盡,無(wú)組織無(wú)紀(jì)律,混亂場(chǎng)面可想而知(出現(xiàn)問(wèn)題,自然也就不可能輕易的發(fā)現(xiàn)和解決)
2.頻繁創(chuàng)建開(kāi)銷大
過(guò)多的線程自然也會(huì)引起上下文切換的開(kāi)銷,平均每個(gè)線程大概需要 1M 左右的大小,頻繁手動(dòng)創(chuàng)建/銷毀線程的代價(jià)是非常巨大的。

所以盡量避免手動(dòng)創(chuàng)建線程,應(yīng)使用線程池統(tǒng)一管理線程

線程池的好處

  1. 降低資源的消耗
    通過(guò)重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀所造成的消耗
  2. 提高響應(yīng)速度
    當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行
  3. 提高線程的可管理性
    線程是稀缺資源,如果無(wú)限制地創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一分配管理、調(diào)優(yōu)和監(jiān)控,控制最大并發(fā)數(shù)。
  4. 能自定義實(shí)現(xiàn)任務(wù)線程隊(duì)列緩存策略和拒絕機(jī)制
  5. 實(shí)現(xiàn)某些功能,如定時(shí)執(zhí)行,周期執(zhí)行等
  6. 隔離線程環(huán)境,比如,交易服務(wù)和搜索服務(wù)在同一臺(tái)服務(wù)器上,分別開(kāi)啟兩個(gè)線程池,交易線程的資源消耗明顯要大。因此,通過(guò)配置獨(dú)立的線程池,將較慢的交易服務(wù)與搜索服務(wù)個(gè)離開(kāi),避免個(gè)服務(wù)線程互相影響。

線程池使用思想/注意事項(xiàng)

ThreadPoolExecutor 提供了四種拒絕策略:
1.AbortPolicy:默認(rèn)的拒絕策略,會(huì) throw RejectedExecutionException 拒絕
2.CallerRunsPolicy:提交任務(wù)的線程自己去執(zhí)行該任務(wù)
3.DiscardOldestPolicy:丟棄最老的任務(wù),其實(shí)就是把最早進(jìn)入工作隊(duì)列的任務(wù)丟棄,然后把新任務(wù)加入到工作隊(duì)列
4.DiscardPolicy:相當(dāng)大膽的策略,直接丟棄任務(wù),沒(méi)有任何異常拋出

不能忽略的線程池拒絕策略,禁止使用Executors創(chuàng)建線程池

原文地址

舉個(gè)例子

假如有一個(gè)工廠,工廠里面有10個(gè)工人,每個(gè)工人同時(shí)只能做一件任務(wù)。
因此只要當(dāng)10個(gè)工人中有工人是空閑的,來(lái)了任務(wù)就分配給空閑的工人做;
當(dāng)10個(gè)工人都有任務(wù)在做時(shí),如果還來(lái)了任務(wù),就把任務(wù)進(jìn)行排隊(duì)等待;
如果說(shuō)新任務(wù)數(shù)目增長(zhǎng)的速度遠(yuǎn)遠(yuǎn)大于工人做任務(wù)的速度,那么此時(shí)工廠主管可能會(huì)想補(bǔ)救措施,比如重新招4個(gè)臨時(shí)工人進(jìn)來(lái);
然后就將任務(wù)也分配給這4個(gè)臨時(shí)工人做;
如果說(shuō)著14個(gè)工人做任務(wù)的速度還是不夠,此時(shí)工廠主管可能就要考慮不再接收新的任務(wù)或者拋棄前面的一些任務(wù)了。
當(dāng)這14個(gè)工人當(dāng)中有人空閑時(shí),而新任務(wù)增長(zhǎng)的速度又比較緩慢,工廠主管可能就考慮辭掉4個(gè)臨時(shí)工了,只保持原來(lái)的10個(gè)工人,畢竟請(qǐng)額外的工人是要花錢的。

Java通過(guò)Executors提供四種線程池,分別為:

newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過(guò)處理需要,可靈活回收空閑線程,若無(wú)可回收,則新建線程。
newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待。
newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行。
newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行。

    執(zhí)行任務(wù),其實(shí)只是把任務(wù)加入任務(wù)隊(duì)列,什么時(shí)候執(zhí)行有線程池管理器決定  
    public void execute(Runnable task) {  
        synchronized (taskQueue) {  
            taskQueue.add(task);  
            taskQueue.notify();  
        }  
    }  
    批量執(zhí)行任務(wù),其實(shí)只是把任務(wù)加入任務(wù)隊(duì)列,什么時(shí)候執(zhí)行有線程池管理器決定  
    public void execute(Runnable[] task) {  
        synchronized (taskQueue) {  
            for (Runnable t : task)  
                taskQueue.add(t);  
            taskQueue.notify();  
        }  
    }  
    批量執(zhí)行任務(wù),其實(shí)只是把任務(wù)加入任務(wù)隊(duì)列,什么時(shí)候執(zhí)行有線程池管理器決定  
    public void execute(List<Runnable> task) {  
        synchronized (taskQueue) {  
            for (Runnable t : task)  
                taskQueue.add(t);  
            taskQueue.notify();  
        }  
    }  

當(dāng)提交一個(gè)新任務(wù)到線程池時(shí),線程池的處理流程為:


屏幕快照 2018-04-23 下午5.35.57.png
屏幕快照 2018-08-28 下午12.39.10.png
屏幕快照 2018-08-28 下午12.39.40.png

1). 線程池判斷核心線程池里的線程是否都在執(zhí)行任務(wù)。
如果不是,則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù)。如果核心線程池里的線程都在執(zhí)行任務(wù),則進(jìn)入下個(gè)流程。
2). 線程池判斷工作隊(duì)列是否已經(jīng)滿。
如果工作隊(duì)列沒(méi)有滿,則將新提交的任務(wù)存儲(chǔ)在這個(gè)工作隊(duì)列里。如果工作隊(duì)列滿了,則進(jìn)入下個(gè)流程。
3). 線程池判斷線程池的線程是否都處于工作狀態(tài)。
如果沒(méi)有,則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù)。如果已經(jīng)滿了,則交給飽和策略來(lái)處理這個(gè)任務(wù)。

ThreadPoolExecutor執(zhí)行execute()方法的示意圖

1). 如果當(dāng)前運(yùn)行的線程少于corePoolSize,則創(chuàng)建新線程來(lái)執(zhí)行任務(wù)(注意,執(zhí)行這一步驟需要獲取全局鎖)
2). 如果運(yùn)行的線程等于或多于corePoolSize,則將任務(wù)加入BlockingQueue。
3). 如果無(wú)法將任務(wù)加入BlockingQueue(隊(duì)列已滿),則創(chuàng)建新的線程來(lái)處理任務(wù)(注意,執(zhí)行這一步驟需要獲取全局鎖)。
4). 如果創(chuàng)建新線程將使當(dāng)前運(yùn)行的線程超出maximumPoolSize,任務(wù)將被拒絕,并調(diào)用RejectedExecutionHandler.rejectedExecution()方法。

源碼分析。上面的流程分析讓我們很直觀的了解的線程池的工作原理,讓我們?cè)偻ㄟ^(guò)源代碼來(lái)看看是如何實(shí)現(xiàn)的。線程池執(zhí)行任務(wù)的方法如下:

 public void execute(Runnable command) {
 if (command == null) throw new NullPointerException();
 // 如果線程數(shù)小于基本線程數(shù),則創(chuàng)建線程并執(zhí)行當(dāng)前任務(wù)
 if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
 // 如果線程數(shù)大于等于基本線程數(shù)或線程創(chuàng)建失敗,則將當(dāng)前任務(wù)放到工作隊(duì)列中。
     if (runState == RUNNING && workQueue.offer(command)) {
       if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command);
 // 如果線程池不處于運(yùn)行中或任務(wù)無(wú)法放入隊(duì)列,并且當(dāng)前線程數(shù)量小于最大允許的線程數(shù)量,則創(chuàng)建一個(gè)線程執(zhí)行任務(wù)
      } else if (!addIfUnderCorePoolSize(command)) {
 // 拋出 RejectedExecutionException異常
         reject(command);// is shutdown or saturated
      }
   }
 }

工作線程。線程池創(chuàng)建線程時(shí),會(huì)將線程封裝成工作線程Worker,Worker在執(zhí)行完任務(wù)后,還會(huì)無(wú)限循環(huán)獲取工作隊(duì)列里的任務(wù)來(lái)執(zhí)行。我們可以從Worker的run方法里看到這點(diǎn):

 public void run() {
   try {
       Runnable task = firstTask;
       firstTask = null;
       while (task != null || (task = getTask()) != null) {
           runTask(task);
           task = null;
       }
     } finally {
       workerDone(this);
     }
 }

線程池的創(chuàng)建
我們可以通過(guò)ThreadPoolExecutor來(lái)創(chuàng)建一個(gè)線程池。

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler);

創(chuàng)建一個(gè)線程池需要輸入幾個(gè)參數(shù):

  • corePoolSize(線程池的基本大小):當(dāng)提交一個(gè)任務(wù)到線程池時(shí),線程池會(huì)創(chuàng)建一個(gè)線程來(lái)執(zhí)行任務(wù),即使其他空閑的基本線程能夠執(zhí)行新任務(wù)也會(huì)創(chuàng)建線程,等到需要執(zhí)行的任務(wù)數(shù)大于線程池基本大小時(shí)就不再創(chuàng)建。如果調(diào)用了線程池的prestartAllCoreThreads方法,線程池會(huì)提前創(chuàng)建并啟動(dòng)所有基本線程。

  • maximumPoolSize(線程池最大大小):線程池允許創(chuàng)建的最大線程數(shù)。如果隊(duì)列滿了,并且已創(chuàng)建的線程數(shù)小于最大線程數(shù),則線程池會(huì)再創(chuàng)建新的線程執(zhí)行任務(wù)。值得注意的是如果使用了無(wú)界的任務(wù)隊(duì)列這個(gè)參數(shù)就沒(méi)什么效果。

  • keepAliveTime(線程活動(dòng)保持時(shí)間):線程池的工作線程空閑后,保持存活的時(shí)間。所以如果任務(wù)很多,
    并且每個(gè)任務(wù)執(zhí)行的時(shí)間比較短,可以調(diào)大這個(gè)時(shí)間,提高線程的利用率。

  • TimeUnit(線程活動(dòng)保持時(shí)間的單位):可選的單位有天(DAYS),小時(shí)(HOURS),分鐘(MINUTES),
    毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

  • runnableTaskQueue(任務(wù)隊(duì)列):用于保存等待執(zhí)行的任務(wù)的阻塞隊(duì)列。可以選擇以下幾個(gè)阻塞隊(duì)列。

1.ArrayBlockingQueue: 是一個(gè)基于數(shù)組結(jié)構(gòu)的有界阻塞隊(duì)列,此隊(duì)列按 FIFO(先進(jìn)先出)原則對(duì)元素進(jìn)行排序。

2.LinkedBlockingQueue: 一個(gè)基于鏈表結(jié)構(gòu)的阻塞隊(duì)列,此隊(duì)列按FIFO (先進(jìn)先出) 排序元素,
吞吐量通常要高于ArrayBlockingQueue。靜態(tài)工廠方法Executors.newFixedThreadPool()使用了這個(gè)隊(duì)列。

3.SynchronousQueue: 一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列。每個(gè)插入操作必須等到另一個(gè)線程調(diào)用移除操作,
否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于LinkedBlockingQueue,
靜態(tài)工廠方法Executors.newCachedThreadPool使用了這個(gè)隊(duì)列。

4.PriorityBlockingQueue: 一個(gè)具有優(yōu)先級(jí)的無(wú)限阻塞隊(duì)列。
  • ThreadFactory:用于設(shè)置創(chuàng)建線程的工廠,可以通過(guò)線程工廠給每個(gè)創(chuàng)建出來(lái)的線程設(shè)置更有意義的名字,Debug和定位問(wèn)題時(shí)非常又幫助。

  • RejectedExecutionHandler(飽和策略):當(dāng)隊(duì)列和線程池都滿了,說(shuō)明線程池處于飽和狀態(tài),那么必須采取一種策略處理提交的新任務(wù)。這個(gè)策略默認(rèn)情況下是AbortPolicy,表示無(wú)法處理新任務(wù)時(shí)拋出異常。以下是JDK1.5提供的四種策略。n AbortPolicy:直接拋出異常。

1.CallerRunsPolicy:只用調(diào)用者所在線程來(lái)運(yùn)行任務(wù)。
2.DiscardOldestPolicy:丟棄隊(duì)列里最近的一個(gè)任務(wù),并執(zhí)行當(dāng)前任務(wù)。
3.DiscardPolicy:不處理,丟棄掉。
4.當(dāng)然也可以根據(jù)應(yīng)用場(chǎng)景需要來(lái)實(shí)現(xiàn)RejectedExecutionHandler接口自定義策略。如記錄日志或持久化不能處理的任務(wù)。
向線程池提交任務(wù)

我們可以使用execute提交的任務(wù),但是execute方法沒(méi)有返回值,所以無(wú)法判斷任務(wù)知否被線程池執(zhí)行成功。通過(guò)以下代碼可知execute方法輸入的任務(wù)是一個(gè)Runnable類的實(shí)例。

 threadsPool.execute(new Runnable() {
     @Override
     public void run() {
     // TODO Auto-generated method stub
     }
   }
);

我們也可以使用submit 方法來(lái)提交任務(wù),它會(huì)返回一個(gè)future,那么我們可以通過(guò)這個(gè)future來(lái)判斷任務(wù)是否執(zhí)行成功,通過(guò)future的get方法來(lái)獲取返回值,get方法會(huì)阻塞住直到任務(wù)完成,而使用get(long timeout, TimeUnit unit)方法則會(huì)阻塞一段時(shí)間后立即返回,這時(shí)有可能任務(wù)沒(méi)有執(zhí)行完。

try {
    Object s = future.get();
} catch (InterruptedException e) {
    // 處理中斷異常
} catch (ExecutionException e) {
    // 處理無(wú)法執(zhí)行任務(wù)異常
} finally {
    // 關(guān)閉線程池
    executor.shutdown();
}
線程池的關(guān)閉

我們可以通過(guò)調(diào)用線程池的shutdown或shutdownNow方法來(lái)關(guān)閉線程池,但是它們的實(shí)現(xiàn)原理不同,shutdown的原理是只是將線程池的狀態(tài)設(shè)置成SHUTDOWN狀態(tài),然后中斷所有沒(méi)有正在執(zhí)行任務(wù)的線程。shutdownNow的原理是遍歷線程池中的工作線程,然后逐個(gè)調(diào)用線程的interrupt方法來(lái)中斷線程,所以無(wú)法響應(yīng)中斷的任務(wù)可能永遠(yuǎn)無(wú)法終止。shutdownNow會(huì)首先將線程池的狀態(tài)設(shè)置成STOP,然后嘗試停止所有的正在執(zhí)行或暫停任務(wù)的線程,并返回等待執(zhí)行任務(wù)的列表。
只要調(diào)用了這兩個(gè)關(guān)閉方法的其中一個(gè),isShutdown方法就會(huì)返回true。當(dāng)所有的任務(wù)都已關(guān)閉后,才表示線程池關(guān)閉成功,這時(shí)調(diào)用isTerminaed方法會(huì)返回true。至于我們應(yīng)該調(diào)用哪一種方法來(lái)關(guān)閉線程池,應(yīng)該由提交到線程池的任務(wù)特性決定,通常調(diào)用shutdown來(lái)關(guān)閉線程池,如果任務(wù)不一定要執(zhí)行完,則可以調(diào)用shutdownNow。

合理的配置線程池

要想合理的配置線程池,就必須首先分析任務(wù)特性,可以從以下幾個(gè)角度來(lái)進(jìn)行分析:

任務(wù)的性質(zhì):CPU密集型任務(wù),IO密集型任務(wù)和混合型任務(wù)。
任務(wù)的優(yōu)先級(jí):高,中和低。
任務(wù)的執(zhí)行時(shí)間:長(zhǎng),中和短。
任務(wù)的依賴性:是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫(kù)連接。

任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開(kāi)處理。CPU密集型任務(wù)配置盡可能少的線程數(shù)量,如配置Ncpu+1個(gè)線程的線程池。IO密集型任務(wù)則由于需要等待IO操作,線程并不是一直在執(zhí)行任務(wù),則配置盡可能多的線程,如2*Ncpu。混合型的任務(wù),如果可以拆分,則將其拆分成一個(gè)CPU密集型任務(wù)和一個(gè)IO密集型任務(wù),只要這兩個(gè)任務(wù)執(zhí)行的時(shí)間相差不是太大,那么分解后執(zhí)行的吞吐率要高于串行執(zhí)行的吞吐率,如果這兩個(gè)任務(wù)執(zhí)行時(shí)間相差太大,則沒(méi)必要進(jìn)行分解。我們可以通過(guò)Runtime.getRuntime().availableProcessors()方法獲得當(dāng)前設(shè)備的CPU個(gè)數(shù)。
優(yōu)先級(jí)不同的任務(wù)可以使用優(yōu)先級(jí)隊(duì)列PriorityBlockingQueue來(lái)處理。它可以讓優(yōu)先級(jí)高的任務(wù)先得到執(zhí)行,需要注意的是如果一直有優(yōu)先級(jí)高的任務(wù)提交到隊(duì)列里,那么優(yōu)先級(jí)低的任務(wù)可能永遠(yuǎn)不能執(zhí)行。
執(zhí)行時(shí)間不同的任務(wù)可以交給不同規(guī)模的線程池來(lái)處理,或者也可以使用優(yōu)先級(jí)隊(duì)列,讓執(zhí)行時(shí)間短的任務(wù)先執(zhí)行。
依賴數(shù)據(jù)庫(kù)連接池的任務(wù),因?yàn)榫€程提交SQL后需要等待數(shù)據(jù)庫(kù)返回結(jié)果,如果等待的時(shí)間越長(zhǎng)CPU空閑時(shí)間就越長(zhǎng),那么線程數(shù)應(yīng)該設(shè)置越大,這樣才能更好的利用CPU。

建議使用有界隊(duì)列
有界隊(duì)列能增加系統(tǒng)的穩(wěn)定性和預(yù)警能力,可以根據(jù)需要設(shè)大一點(diǎn),比如幾千。有一次我們組使用的后臺(tái)任務(wù)線程池的隊(duì)列和線程池全滿了,不斷的拋出拋棄任務(wù)的異常,通過(guò)排查發(fā)現(xiàn)是數(shù)據(jù)庫(kù)出現(xiàn)了問(wèn)題,導(dǎo)致執(zhí)行SQL變得非常緩慢,因?yàn)楹笈_(tái)任務(wù)線程池里的任務(wù)全是需要向數(shù)據(jù)庫(kù)查詢和插入數(shù)據(jù)的,所以導(dǎo)致線程池里的工作線程全部阻塞住,任務(wù)積壓在線程池里。如果當(dāng)時(shí)我們?cè)O(shè)置成無(wú)界隊(duì)列,線程池的隊(duì)列就會(huì)越來(lái)越多,有可能會(huì)撐滿內(nèi)存,導(dǎo)致整個(gè)系統(tǒng)不可用,而不只是后臺(tái)任務(wù)出現(xiàn)問(wèn)題。當(dāng)然我們的系統(tǒng)所有的任務(wù)是用的單獨(dú)的服務(wù)器部署的,而我們使用不同規(guī)模的線程池跑不同類型的任務(wù),但是出現(xiàn)這樣問(wèn)題時(shí)也會(huì)影響到其他任務(wù)。

線程池的監(jiān)控

如果在系統(tǒng)中大量使用線程池,則有必要對(duì)線程池進(jìn)行監(jiān)控,方便在出現(xiàn)問(wèn)題時(shí),可以根據(jù)線程池的使用狀況快速定位問(wèn)題。可以通過(guò)線程池提供的參數(shù)進(jìn)行監(jiān)控,在監(jiān)控線程池的時(shí)候可以使用以下屬性

taskCount:線程池需要執(zhí)行的任務(wù)數(shù)量。
completedTaskCount:線程池在運(yùn)行過(guò)程中已完成的任務(wù)數(shù)量,小于或等于taskCount。
largestPoolSize:線程池里曾經(jīng)創(chuàng)建過(guò)的最大線程數(shù)量。通過(guò)這個(gè)數(shù)據(jù)可以知道線程池是否曾經(jīng)滿過(guò)。如該數(shù)值等于線程池的最大大小,則表示線程池曾經(jīng)滿過(guò)。
getPoolSize:線程池的線程數(shù)量。如果線程池不銷毀的話,線程池里的線程不會(huì)自動(dòng)銷毀,所以這個(gè)大小只增不減。
getActiveCount:獲取活動(dòng)的線程數(shù)。

通過(guò)擴(kuò)展線程池進(jìn)行監(jiān)控。可以通過(guò)繼承線程池來(lái)自定義線程池,重寫(xiě)線程池的beforeExecute、afterExecute和terminated方法,也可以在任務(wù)執(zhí)行前、執(zhí)行后和線程池關(guān)閉前執(zhí)行一些代碼來(lái)進(jìn)行監(jiān)控。例如,監(jiān)控任務(wù)的平均執(zhí)行時(shí)間、最大執(zhí)行時(shí)間和最小執(zhí)行時(shí)間等。

https://blog.csdn.net/u012702547/article/details/52259529

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容