成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

一看就懂的Java線程池分析詳解

Yangder / 480人閱讀

摘要:任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開(kāi)處理。線程池在運(yùn)行過(guò)程中已完成的任務(wù)數(shù)量。如等于線程池的最大大小,則表示線程池曾經(jīng)滿了。線程池的線程數(shù)量。獲取活動(dòng)的線程數(shù)。通過(guò)擴(kuò)展線程池進(jìn)行監(jiān)控??蚣馨ň€程池,,,,,,等。

Java線程池

[toc]

什么是線程池

線程池就是有N個(gè)子線程共同在運(yùn)行的線程組合。

舉個(gè)容易理解的例子:有個(gè)線程組合(即線程池,咱可以比喻為一個(gè)公司),里面有3個(gè)子線程(當(dāng)作3個(gè)員工吧),待命干活。
只要客戶告訴他一個(gè)任務(wù)(比如搬磚),公司就會(huì)挑一個(gè)員工來(lái)做;

如果很多客戶都找,3個(gè)忙不過(guò)來(lái),那公司可以再雇2個(gè)人,但本公司運(yùn)營(yíng)能力有限,辦公室也不大,最多就雇傭5個(gè)人,如果還忙不過(guò)來(lái),那這些送來(lái)的任務(wù)就排隊(duì)了。一件一件做完。

ThreadPoolExecutor簡(jiǎn)介

java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個(gè)類,因此如果要透徹地了解Java中的線程池,必須先了解這個(gè)類。下面我們來(lái)看一下ThreadPoolExecutor類的具體實(shí)現(xiàn)源碼:

ThreadPoolExecutor類中提供了四個(gè)構(gòu)造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue workQueue,RejectedExecutionHandler handler);
 
   
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler);
        
}

從上面的代碼可以得知,ThreadPoolExecutor繼承了AbstractExecutorService類,并提供了四個(gè)構(gòu)造器,事實(shí)上,通過(guò)觀察每個(gè)構(gòu)造器的源碼具體實(shí)現(xiàn),發(fā)現(xiàn)前面三個(gè)構(gòu)造器都是調(diào)用的第四個(gè)構(gòu)造器進(jìn)行的初始化工作。

corePoolSize 線程池維護(hù)線程的最少數(shù)量。

需要注意的是在初創(chuàng)建線程池時(shí)線程不會(huì)立即啟動(dòng),直到有任務(wù)提交才開(kāi)始啟動(dòng)線程并逐漸時(shí)線程數(shù)目達(dá)到corePoolSize。若想一開(kāi)始就創(chuàng)建所有核心線程需調(diào)用prestartAllCoreThreads方法。

maximumPoolSize-池中允許的最大線程數(shù)。

需要注意的是當(dāng)核心線程滿且阻塞隊(duì)列也滿時(shí)才會(huì)判斷當(dāng)前線程數(shù)是否小于最大線程數(shù),并決定是否創(chuàng)建新線程。

keepAliveTime - 線程池維護(hù)線程所允許的空閑時(shí)間

當(dāng)線程數(shù)大于核心時(shí),多于的空閑線程最多存活時(shí)間 
默認(rèn)情況下,只有當(dāng)線程池中的線程數(shù)大于corePoolSize時(shí),keepAliveTime才會(huì)起作用,直到線程池中的線程數(shù)不大于corePoolSize,即當(dāng)線程池中的線程數(shù)大于corePoolSize時(shí),如果一個(gè)線程空閑的時(shí)間達(dá)到keepAliveTime,則會(huì)終止,直到線程池中的線程數(shù)不超過(guò)corePoolSize。但是如果調(diào)用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數(shù)不大于corePoolSize時(shí),keepAliveTime參數(shù)也會(huì)起作用,直到線程池中的線程數(shù)為0

unit - keepAliveTime 參數(shù)的時(shí)間單位,有7種取值。

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小時(shí)
TimeUnit.MINUTES;           //分鐘
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //納秒

workQueue - 當(dāng)線程數(shù)目超過(guò)核心線程數(shù)時(shí)用于保存任務(wù)的隊(duì)列。主要有3種類型的BlockingQueue可供選擇:有界隊(duì)列,無(wú)界隊(duì)列和同步移交。

ArrayBlockingQueue;  //有界隊(duì)列
LinkedBlockingQueue; //無(wú)界隊(duì)列
SynchronousQueue;  //同步移交
PriorityBlockingQueue; //一個(gè)具有優(yōu)先級(jí)得無(wú)限阻塞隊(duì)列。

threadFactory - 執(zhí)行程序創(chuàng)建新線程時(shí)使用的工廠。

handler - 阻塞隊(duì)列已滿且線程數(shù)達(dá)到最大值時(shí)所采取的飽和策略。java默認(rèn)提供了4種飽和策略的實(shí)現(xiàn)方式:中止、拋棄、拋棄最舊的、調(diào)用者運(yùn)行。

ThreadPoolExecutor.AbortPolicy();  拋出java.util.concurrent.RejectedExecutionException異常 
ThreadPoolExecutor.CallerRunsPolicy();  重試添加當(dāng)前的任務(wù),他會(huì)自動(dòng)重復(fù)調(diào)用execute()方法 
ThreadPoolExecutor.DiscardOldestPolicy(); 拋棄舊的任務(wù)  
ThreadPoolExecutor.DiscardPolicy();  拋棄當(dāng)前的任務(wù) 
當(dāng)然也可以根據(jù)應(yīng)用場(chǎng)景需要來(lái)實(shí)現(xiàn)`RejectedExecutionHandler`接口自定義策略。如記錄日志或持久化不能處理的任務(wù)。
向上翻源碼

從上面給出的ThreadPoolExecutor類的代碼可以知道,ThreadPoolExecutor繼承了AbstractExecutorService,我們來(lái)看一下AbstractExecutorService的實(shí)現(xiàn):

public abstract class AbstractExecutorService implements ExecutorService {
 
    protected  RunnableFuture newTaskFor(Runnable runnable, T value) { };
    protected  RunnableFuture newTaskFor(Callable callable) { };
    public Future submit(Runnable task) {};
    public  Future submit(Runnable task, T result) { };
    public  Future submit(Callable task) { };
    private  T doInvokeAny(Collection> tasks,
                            boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public  T invokeAny(Collection> tasks)
        throws InterruptedException, ExecutionException {
    };
    public  T invokeAny(Collection> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public  List> invokeAll(Collection> tasks)
        throws InterruptedException {
    };
    public  List> invokeAll(Collection> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
    };
}

AbstractExecutorService是一個(gè)抽象類,它實(shí)現(xiàn)了ExecutorService接口。

我們接著看ExecutorService接口的實(shí)現(xiàn):

public interface ExecutorService extends Executor {
 
    void shutdown();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException;
    
     Future submit(Callable task);
    
     Future submit(Runnable task, T result);
    
    Future submit(Runnable task);
    
     List> invokeAll(Collection> tasks)throws InterruptedException;
    
     List> invokeAll(Collection> tasks,long timeout, TimeUnit unit)throws InterruptedException;
 
     T invokeAny(Collection> tasks)throws InterruptedException, ExecutionException;
    
     T invokeAny(Collection> tasks,long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}

ExecutorService又是繼承了Executor接口,我們看一下Executor接口的實(shí)現(xiàn):

public interface Executor {
    void execute(Runnable command);
}

到這里,大家應(yīng)該明白了ThreadPoolExecutor、AbstractExecutorService、ExecutorServiceExecutor幾個(gè)之間的關(guān)系了。

Executor是一個(gè)頂層接口,在它里面只聲明了一個(gè)方法execute(Runnable),返回值為void,參數(shù)為Runnable類型,從字面意思可以理解,就是用來(lái)執(zhí)行傳進(jìn)去的任務(wù)的;

然后ExecutorService接口繼承了Executor接口,并聲明了一些方法:submit、invokeAll、invokeAny以及shutDown等;

抽象類AbstractExecutorService實(shí)現(xiàn)了ExecutorService接口,基本實(shí)現(xiàn)了ExecutorService中聲明的所有方法;

在ThreadPoolExecutor類中有幾個(gè)非常重要的方法:

execute()
submit()
shutdown()
shutdownNow()

execute()方法實(shí)際上是Executor中聲明的方法,在ThreadPoolExecutor進(jìn)行了具體的實(shí)現(xiàn),這個(gè)方法是ThreadPoolExecutor的核心方法,通過(guò)這個(gè)方法可以向線程池提交一個(gè)任務(wù),交由線程池去執(zhí)行。

submit()方法是在ExecutorService中聲明的方法,在AbstractExecutorService就已經(jīng)有了具體的實(shí)現(xiàn),在ThreadPoolExecutor中并沒(méi)有對(duì)其進(jìn)行重寫(xiě),這個(gè)方法也是用來(lái)向線程池提交任務(wù)的,但是它和execute()方法不同,它能夠返回任務(wù)執(zhí)行的結(jié)果,去看submit()方法的實(shí)現(xiàn),會(huì)發(fā)現(xiàn)它實(shí)際上還是調(diào)用的execute()方法,只不過(guò)它利用了Future來(lái)獲取任務(wù)執(zhí)行結(jié)果。

shutdown()和shutdownNow()是用來(lái)關(guān)閉線程池的。

還有很多其他的方法比如:getQueue() 、getPoolSize()、getActiveCount()、getCompletedTaskCount()等獲取與線程池相關(guān)屬性的方法,自行查閱API。

線程池的流程分析

線程池的主要工作流程如下圖:
Alt text

從上圖我們可以看出,當(dāng)提交一個(gè)新任務(wù)到線程池時(shí),線程池的處理流程如下:

首先線程池判斷基本線程池是否已滿?沒(méi)滿,創(chuàng)建一個(gè)工作線程來(lái)執(zhí)行任務(wù)。滿了,則進(jìn)入下個(gè)流程。

其次線程池判斷工作隊(duì)列是否已滿?沒(méi)滿,則將新提交的任務(wù)存儲(chǔ)在工作隊(duì)列里。滿了,則進(jìn)入下個(gè)流程。

最后線程池判斷整個(gè)線程池是否已滿?沒(méi)滿,則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù),滿了,則交給飽和策略來(lái)處理這個(gè)任務(wù)。

源碼分析

線程池執(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 (!addIfUnderMaximumPoolSize(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);
      }
}
合理的配置線程池

要想合理的配置線程池,就必須首先分析任務(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ù)是用的多帶帶的服務(wù)器部署的,而我們使用不同規(guī)模的線程池跑不同類型的任務(wù),但是出現(xiàn)這樣問(wèn)題時(shí)也會(huì)影響到其他任務(wù)。
線程池的監(jiā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ù)可以知道線程池是否滿過(guò)。如等于線程池的最大大小,則表示線程池曾經(jīng)滿了。

getPoolSize:線程池的線程數(shù)量。如果線程池不銷毀的話,池里的線程不會(huì)自動(dòng)銷毀,所以這個(gè)大小只增不減。

getActiveCount:獲取活動(dòng)的線程數(shù)。

通過(guò)擴(kuò)展線程池進(jìn)行監(jiān)控。通過(guò)繼承線程池并重寫(xiě)線程池的beforeExecute,afterExecute,terminated方法,我們可以在任務(wù)執(zhí)行前,執(zhí)行后和線程池關(guān)閉前干一些事情。

如監(jiān)控任務(wù)的平均執(zhí)行時(shí)間,最大執(zhí)行時(shí)間和最小執(zhí)行時(shí)間等。這幾個(gè)方法在線程池里是空方法。如:

protected void beforeExecute(Thread t, Runnable r) { }

常用的幾種線程池

什么是 Executor 框架 ? (面試題)

Executor框架在Java 5中被引入,Executor 框架是一個(gè)根據(jù)一組執(zhí)行策略調(diào)用、調(diào)度、執(zhí)行和控制的異步任務(wù)的框架。

Executor 框架包括:線程池,Executor,Executors,ExecutorService,CompletionService,F(xiàn)uture,Callable 等。

不過(guò)在java doc中,并不提倡我們直接使用ThreadPoolExecutor,而是使用Executors類中提供的幾個(gè)靜態(tài)方法來(lái)創(chuàng)建四種線程池

注意在全新的阿里編程規(guī)約里面不推薦使用Executors提供的靜態(tài)方法創(chuàng)建線程。

newCachedThreadPool 是一個(gè)可根據(jù)需要?jiǎng)?chuàng)建新線程的線程池,創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過(guò)處理需要,可靈活回收空閑線程,若無(wú)可回收,則新建線程。。

 ExecutorService cachedThreadPool = Executors.newCachedThreadPool();   
   cachedThreadPool.execute(new Runnable() {  
    public void run() {  
     System.out.println("runing.....");  
    }  
   });   
 }  

--

newSingleThreadExecutor 創(chuàng)建是一個(gè)單線程池,也就是該線程池只有一個(gè)線程在工作,所有的任務(wù)是串行執(zhí)行的。如果這個(gè)唯一的線程因?yàn)楫惓=Y(jié)束,那么會(huì)有一個(gè)新的線程來(lái)替代它,此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行。

 ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(););   
   singleThreadExecutor.execute(new Runnable() {  
    public void run() {  
     System.out.println("runing.....");  
    }  
   });   
 }  

--

newFixedThreadPool 創(chuàng)建固定大小的線程池,每次提交一個(gè)任務(wù)就創(chuàng)建一個(gè)線程,直到線程達(dá)到線程池的最大大小。線程池的大小一旦達(dá)到最大值就會(huì)保持不變,如果某個(gè)線程因?yàn)閳?zhí)行異常而結(jié)束,那么線程池會(huì)補(bǔ)充一個(gè)新線程。

定長(zhǎng)線程池的大小最好根據(jù)系統(tǒng)資源進(jìn)行設(shè)置。如Runtime.getRuntime().availableProcessors()

 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);   
   fixedThreadPool .execute(new Runnable() {  
    public void run() {  
     System.out.println("runing.....");  
    }  
   });   
 }  

--

newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行。

 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);    
   scheduledThreadPool.schedule(new Runnable() {  
    public void run() {  
     System.out.println("runing.....");  
    }  
   }, 3, TimeUnit.SECONDS);   // 表示延遲3秒執(zhí)行。
 }  
使用線程池的風(fēng)險(xiǎn)

雖然線程池是構(gòu)建多線程應(yīng)用程序的強(qiáng)大機(jī)制,但使用它并不是沒(méi)有風(fēng)險(xiǎn)的。

用線程池構(gòu)建的應(yīng)用程序容易遭受任何其它多線程應(yīng)用程序容易遭受的所有并發(fā)風(fēng)險(xiǎn),諸如同步錯(cuò)誤和死鎖,它還容易遭受特定于線程池的少數(shù)其它風(fēng)險(xiǎn),諸如與池有關(guān)的死鎖、資源不足和線程泄漏。

死鎖

任何多線程應(yīng)用程序都有死鎖風(fēng)險(xiǎn)。當(dāng)一組進(jìn)程或線程中的每一個(gè)都在等待一個(gè)只有該組中另一個(gè)進(jìn)程才能引起的事件時(shí),我們就說(shuō)這組進(jìn)程或線程 死鎖了。

死鎖的最簡(jiǎn)單情形是:線程 A 持有對(duì)象 X 的獨(dú)占鎖,并且在等待對(duì)象 Y 的鎖,而線程 B 持有對(duì)象 Y 的獨(dú)占鎖,卻在等待對(duì)象 X 的鎖。除非有某種方法來(lái)打破對(duì)鎖的等待(Java 鎖定不支持這種方法),否則死鎖的線程將永遠(yuǎn)等下去。

雖然任何多線程程序中都有死鎖的風(fēng)險(xiǎn),但線程池卻引入了另一種死鎖可能,在那種情況下,所有池線程都在執(zhí)行已阻塞的等待隊(duì)列中另一任務(wù)的執(zhí)行結(jié)果的任務(wù),但這一任務(wù)卻因?yàn)闆](méi)有未被占用的線程而不能運(yùn)行。
當(dāng)線程池被用來(lái)實(shí)現(xiàn)涉及許多交互對(duì)象的模擬,被模擬的對(duì)象可以相互發(fā)送查詢,這些查詢接下來(lái)作為排隊(duì)的任務(wù)執(zhí)行,查詢對(duì)象又同步等待著響應(yīng)時(shí),會(huì)發(fā)生這種情況。

資源不足

線程池的一個(gè)優(yōu)點(diǎn)在于:相對(duì)于其它替代調(diào)度機(jī)制言,它們通常執(zhí)行得很好。但只有恰當(dāng)?shù)卣{(diào)整了線程池大小時(shí)才是這樣的。線程消耗包括內(nèi)存和其它系統(tǒng)資源在內(nèi)的大量資源。除了 Thread 對(duì)象所需的內(nèi)存之外,每個(gè)線程都需要兩個(gè)可能很大的執(zhí)行調(diào)用堆棧。除此以外,JVM 可能會(huì)為每個(gè) Java 線程創(chuàng)建一個(gè)本機(jī)線程,這些本機(jī)線程將消耗額外的系統(tǒng)資源。最后,雖然線程之間切換的調(diào)度開(kāi)銷很小,但如果有很多線程,環(huán)境切換也可能嚴(yán)重地影響程序的性能。

如果線程池太大,那么被那些線程消耗的資源可能嚴(yán)重地影響系統(tǒng)性能。在線程之間進(jìn)行切換將會(huì)浪費(fèi)時(shí)間,而且使用超出比您實(shí)際需要的線程可能會(huì)引起資源匱乏問(wèn)題,因?yàn)槌鼐€程正在消耗一些資源,而這些資源可能會(huì)被其它任務(wù)更有效地利用。除了線程自身所使用的資源以外,服務(wù)請(qǐng)求時(shí)所做的工作可能需要其它資源,例如 JDBC 連接、套接字或文件。
這些也都是有限資源,有太多的并發(fā)請(qǐng)求也可能引起失效,例如不能分配 JDBC 連接。

并發(fā)錯(cuò)誤

線程池和其它排隊(duì)機(jī)制依靠使用 wait()notify() 方法,這兩個(gè)方法都難于使用。如果編碼不正確,那么可能丟失通知,導(dǎo)致線程保持空閑狀態(tài),盡管隊(duì)列中有工作要處理。使用這些方法時(shí),必須格外小心。而最好使用現(xiàn)有的、已經(jīng)知道能工作的實(shí)現(xiàn),例如 util.concurrent 包。

線程泄漏

各種類型的線程池中一個(gè)嚴(yán)重的風(fēng)險(xiǎn)是線程泄漏,當(dāng)從池中除去一個(gè)線程以執(zhí)行一項(xiàng)任務(wù),而在任務(wù)完成后該線程卻沒(méi)有返回池時(shí),會(huì)發(fā)生這種情況。發(fā)生線程泄漏的一種情形出現(xiàn)在任務(wù)拋出一個(gè) RuntimeException 或一個(gè) Error 時(shí)。如果池類沒(méi)有捕捉到它們,那么線程只會(huì)退出而線程池的大小將會(huì)永久減少一個(gè)。當(dāng)這種情況發(fā)生的次數(shù)足夠多時(shí),線程池最終就為空,而且系統(tǒng)將停止,因?yàn)闆](méi)有可用的線程來(lái)處理任務(wù)。

有些任務(wù)可能會(huì)永遠(yuǎn)等待某些資源或來(lái)自用戶的輸入,而這些資源又不能保證變得可用,用戶可能也已經(jīng)回家了,諸如此類的任務(wù)會(huì)永久停止,而這些停止的任務(wù)也會(huì)引起和線程泄漏同樣的問(wèn)題。如果某個(gè)線程被這樣一個(gè)任務(wù)永久地消耗著,那么它實(shí)際上就被從池除去了。對(duì)于這樣的任務(wù),應(yīng)該要么只給予它們自己的線程,要么只讓它們等待有限的時(shí)間。

請(qǐng)求過(guò)載

僅僅是請(qǐng)求就壓垮了服務(wù)器,這種情況是可能的。在這種情形下,我們可能不想將每個(gè)到來(lái)的請(qǐng)求都排隊(duì)到我們的工作隊(duì)列,因?yàn)榕旁陉?duì)列中等待執(zhí)行的任務(wù)可能會(huì)消耗太多的系統(tǒng)資源并引起資源缺乏。在這種情形下決定如何做取決于您自己;在某些情況下,您可以簡(jiǎn)單地拋棄請(qǐng)求,依靠更高級(jí)別的協(xié)議稍后重試請(qǐng)求,您也可以用一個(gè)指出服務(wù)器暫時(shí)很忙的響應(yīng)來(lái)拒絕請(qǐng)求。

可選擇的阻塞隊(duì)列BlockingQueue詳解

重復(fù)看一下新任務(wù)進(jìn)入時(shí)線程池的執(zhí)行策略:

如果運(yùn)行的線程少于corePoolSize,則 Executor始終首選添加新的線程,而不進(jìn)行排隊(duì)。

如果運(yùn)行的線程大于等于 corePoolSize,則 Executor始終首選將請(qǐng)求加入隊(duì)列,而不添加新的線程。

如果無(wú)法將請(qǐng)求加入隊(duì)列,則創(chuàng)建新的線程,除非創(chuàng)建此線程超出 maximumPoolSize,在這種情況下,任務(wù)將被拒絕。

主要有3種類型的BlockingQueue

無(wú)界隊(duì)列

隊(duì)列大小無(wú)限制,常用的為無(wú)界的LinkedBlockingQueue,將導(dǎo)致在所有 corePoolSize 線程都忙時(shí)新任務(wù)在隊(duì)列中等待。這樣,創(chuàng)建的線程就不會(huì)超過(guò) corePoolSize

應(yīng)用場(chǎng)景:當(dāng)每個(gè)任務(wù)完全獨(dú)立于其他任務(wù),即任務(wù)執(zhí)行互不影響時(shí),適合于使用無(wú)界隊(duì)列。
例如,在 Web 頁(yè)服務(wù)器中。這種排隊(duì)可用于處理瞬態(tài)突發(fā)請(qǐng)求,當(dāng)命令以超過(guò)隊(duì)列所能處理的平均數(shù)連續(xù)到達(dá)時(shí),此策略允許無(wú)界線程具有增長(zhǎng)的可能性。

有界隊(duì)列

常用的有兩類,一類是遵循FIFO原則的隊(duì)列如ArrayBlockingQueue與有界的LinkedBlockingQueue,另一類是優(yōu)先級(jí)隊(duì)列如PriorityBlockingQueue。PriorityBlockingQueue中的優(yōu)先級(jí)由任務(wù)的Comparator決定。

使用有界隊(duì)列時(shí)隊(duì)列大小需和線程池大小互相配合,線程池較小有界隊(duì)列較大時(shí)可減少內(nèi)存消耗,降低cpu使用率和上下文切換,但是可能會(huì)限制系統(tǒng)吞吐量。

當(dāng)使用有限的 maximumPoolSizes 時(shí),有界隊(duì)列(如 ArrayBlockingQueue)有助于防止資源耗盡,但是可能較難調(diào)整和控制。隊(duì)列大小和最大池大小可能需要相互折衷,使用大型隊(duì)列和小型池可以最大限度地降低 CPU 使用率、操作系統(tǒng)資源和上下文切換開(kāi)銷,但是可能導(dǎo)致人工降低吞吐量。

如果任務(wù)頻繁阻塞(例如,如果它們是 I/O 邊界),則系統(tǒng)可能為超過(guò)您許可的更多線程安排時(shí)間。使用小型隊(duì)列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調(diào)度開(kāi)銷,這樣也會(huì)降低吞吐量。

同步移交

(直接提交) 如果不希望任務(wù)在隊(duì)列中等待而是希望將任務(wù)直接移交給工作線程,可使用SynchronousQueue作為等待隊(duì)列。SynchronousQueue不是一個(gè)真正的隊(duì)列,而是一種線程之間移交的機(jī)制。要將一個(gè)元素放入SynchronousQueue中,必須有另一個(gè)線程正在等待接收這個(gè)元素。只有在使用無(wú)界線程池或者有飽和策略時(shí)才建議使用該隊(duì)列。

工作隊(duì)列的默認(rèn)選項(xiàng)是 SynchronousQueue,此策略可以 避免在處理可能具有內(nèi)部依賴性的請(qǐng)求集時(shí)出現(xiàn)鎖。

該Queue本身的特性,在某次添加元素后必須等待其他線程取走后才能繼續(xù)添加。

可選擇的飽和策略RejectedExecutionHandler詳解

JDK主要提供了4種飽和策略供選擇。4種策略都做為靜態(tài)內(nèi)部類在ThreadPoolExcutor中進(jìn)行實(shí)現(xiàn)。

AbortPolicy中止策略
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }

使用該策略時(shí)在飽和時(shí)會(huì)拋出RejectedExecutionException(繼承自RuntimeException),調(diào)用者可捕獲該異常自行處理。

DiscardPolicy拋棄策略
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }

如代碼所示,不做任何處理直接拋棄任務(wù)

DiscardOldestPolicy拋棄舊任務(wù)策略
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }

如代碼,先將阻塞隊(duì)列中的頭元素出隊(duì)拋棄,再嘗試提交任務(wù)。如果此時(shí)阻塞隊(duì)列使用PriorityBlockingQueue優(yōu)先級(jí)隊(duì)列,將會(huì)導(dǎo)致優(yōu)先級(jí)最高的任務(wù)被拋棄,因此不建議將該種策略配合優(yōu)先級(jí)隊(duì)列使用。

CallerRunsPolicy調(diào)用者運(yùn)行
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }

既不拋棄任務(wù)也不拋出異常,直接運(yùn)行任務(wù)的run方法,換言之將任務(wù)回退給調(diào)用者來(lái)直接運(yùn)行。使用該策略時(shí)線程池飽和后將由調(diào)用線程池的主線程自己來(lái)執(zhí)行任務(wù),因此在執(zhí)行任務(wù)的這段時(shí)間里主線程無(wú)法再提交新任務(wù),從而使線程池中工作線程有時(shí)間將正在處理的任務(wù)處理完成。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/68819.html

相關(guān)文章

  • 一看懂的javascript全等于與等于

    Javascript只有六個(gè)假值(用在條件if的判斷) showImg(https://segmentfault.com/img/bVLiHL?w=424&h=346); 全等于 類型不同,返回false類型相同,則 showImg(https://segmentfault.com/img/bVLiHS?w=476&h=341); 等于 類型相同:同上=== 類型不同:嘗試類型轉(zhuǎn)換==【不是真值和...

    Apollo 評(píng)論0 收藏0
  • 一看懂的module.exports/exports與module.export/export d

    摘要:命令規(guī)定的是對(duì)外的接口,必須與模塊內(nèi)部的變量建立一一對(duì)應(yīng)關(guān)系。意思是導(dǎo)出的不是一個(gè)具體的數(shù)值,而是一個(gè)對(duì)象命令接受一對(duì)大括號(hào),里面指定要從其他模塊導(dǎo)入的變量名。大括號(hào)里面的變量名,必須與被導(dǎo)入模塊對(duì)外接口的名稱相同。 一、module.exports與exports nodeJS采用commonJs規(guī)范,當(dāng)前文件是一個(gè)模塊(module)私有域,通過(guò)exports屬性導(dǎo)出,通過(guò)re...

    ZoomQuiet 評(píng)論0 收藏0
  • 一看懂的JS抽象語(yǔ)法樹(shù)

    摘要:前言是現(xiàn)在幾乎每個(gè)項(xiàng)目中必備的一個(gè)東西,但是其工作原理避不開(kāi)對(duì)的解析在生成的過(guò)程,有引擎,早期了項(xiàng)目,了解這個(gè)之前我們先來(lái)看看這種引擎解析出來(lái)是什么東西。 前言 babel是現(xiàn)在幾乎每個(gè)項(xiàng)目中必備的一個(gè)東西,但是其工作原理避不開(kāi)對(duì)js的解析在生成的過(guò)程,babel有引擎babylon,早期fork了項(xiàng)目acron,了解這個(gè)之前我們先來(lái)看看這種引擎解析出來(lái)是什么東西。不光是babel還有...

    HackerShell 評(píng)論0 收藏0
  • 一看懂的例子告訴你用react-redux的正確姿勢(shì)

    摘要:很多小白在看過(guò)很多教程之后仍然在敲代碼的時(shí)候不清楚應(yīng)該以什么樣的步驟進(jìn)行,那么這篇文章就一步一步分解整個(gè)過(guò)程,慢動(dòng)作回放讓大家看的清清楚楚明明白白。另外,中視圖部分最好單獨(dú)出來(lái),放在新建一個(gè)文件夾目錄下,并被名為引用,把其他邏輯部分放后者。 whay write this: 很多小白在看過(guò)很多教程之后仍然在敲代碼的時(shí)候不清楚應(yīng)該以什么樣的步驟進(jìn)行,那么這篇文章就一步一步分解整個(gè)過(guò)程,慢...

    DobbyKim 評(píng)論0 收藏0
  • 云服務(wù)器上行帶寬/下行帶寬/出網(wǎng)帶寬/入網(wǎng)帶寬詳解(一看就懂)

    摘要:今天,云服務(wù)器網(wǎng)來(lái)詳細(xì)說(shuō)下云服務(wù)器公網(wǎng)帶寬上行帶寬下行帶寬出網(wǎng)流量和入網(wǎng)流量,本文所述的帶寬是指公網(wǎng)帶寬,內(nèi)網(wǎng)帶寬是免費(fèi)的。什么是服務(wù)器上行帶寬?花錢(qián)購(gòu)買(mǎi)的公網(wǎng)帶寬是上行還是下行?用戶花錢(qián)購(gòu)買(mǎi)公網(wǎng)帶寬是上行帶寬,下行帶寬不收費(fèi),上行帶寬是指云服務(wù)器出網(wǎng)帶寬,流量流出云服務(wù)器的方向;那么,如果從云服務(wù)器下載軟件,這就是下行帶寬,也是流量流入云服務(wù)器方向的帶寬。今天,云服務(wù)器網(wǎng)(yuntue.c...

    番茄西紅柿 評(píng)論0 收藏2637

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<