中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

關(guān)于線(xiàn)程池的十個(gè)熱門(mén)問(wèn)題

這篇文章主要介紹“關(guān)于線(xiàn)程池的十個(gè)熱門(mén)問(wèn)題”,在日常操作中,相信很多人在關(guān)于線(xiàn)程池的十個(gè)熱門(mén)問(wèn)題問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”關(guān)于線(xiàn)程池的十個(gè)熱門(mén)問(wèn)題”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

懷柔網(wǎng)站建設(shè)公司成都創(chuàng)新互聯(lián)公司,懷柔網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為懷柔上1000家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)營(yíng)銷(xiāo)網(wǎng)站建設(shè)要多少錢(qián),請(qǐng)找那個(gè)售后服務(wù)好的懷柔做網(wǎng)站的公司定做!

《Java開(kāi)發(fā)手冊(cè)》中強(qiáng)調(diào),線(xiàn)程資源必須通過(guò)線(xiàn)程池提供,而創(chuàng)建線(xiàn)程池必須使用ThreadPoolExecutor。手冊(cè)主要強(qiáng)調(diào)利用線(xiàn)程池避免兩個(gè)問(wèn)題,一是線(xiàn)程過(guò)渡切換,二是避免請(qǐng)求過(guò)多時(shí)造成OOM。但是如果參數(shù)配置錯(cuò)誤,還是會(huì)引發(fā)上面的兩個(gè)問(wèn)題。所以本節(jié)我們主要是討論ThreadPoolExecutor的一些技術(shù)細(xì)節(jié),并且給出幾個(gè)常用的最佳實(shí)踐建議。

我在查找資料的過(guò)程中,發(fā)現(xiàn)有些問(wèn)題存在爭(zhēng)議。后面發(fā)現(xiàn),一部分原因是因?yàn)椴煌琂DK版本的現(xiàn)實(shí)是有差異的。因此,下面的分析是基于當(dāng)下最常用的版本JDK1.8,并且對(duì)于存在爭(zhēng)議的問(wèn)題,我們分析源碼,源碼才是最準(zhǔn)確的。

1 corePoolSize=0會(huì)怎么樣

這是一個(gè)爭(zhēng)議點(diǎn)。我發(fā)現(xiàn)大部分博文,不論是國(guó)內(nèi)的還是國(guó)外的,都是這樣回答這個(gè)問(wèn)題的:

提交任務(wù)后,先判斷當(dāng)前池中線(xiàn)程數(shù)是否小于corePoolSize,如果小于,則創(chuàng)建新線(xiàn)程執(zhí)行這個(gè)任務(wù)。
否者,判斷等待隊(duì)列是否已滿(mǎn),如果沒(méi)有滿(mǎn),則添加到等待隊(duì)列。
否者,判斷當(dāng)前池中線(xiàn)程數(shù)是否大于maximumPoolSize,如果大于則拒絕。
否者,創(chuàng)建一個(gè)新的線(xiàn)程執(zhí)行這個(gè)任務(wù)。
按照上面的描述,如果corePoolSize=0,則會(huì)判斷等待隊(duì)列的容量,如果還有容量,則排隊(duì),并且不會(huì)創(chuàng)建新的線(xiàn)程。

—— 但其實(shí),這是老版本的實(shí)現(xiàn)方式,從1.6之后,實(shí)現(xiàn)方式就變了。我們直接看execute的源碼(submit也依賴(lài)它),我備注出了關(guān)鍵一行:

int c = ctl.get();        if (workerCountOf(c) < corePoolSize) {            if (addWorker(command, true))                return;            c = ctl.get();        }        if (isRunning(c) && workQueue.offer(command)) {            int recheck = ctl.get();            if (! isRunning(recheck) && remove(command))                reject(command);            // 注意這一行代碼,添加到等待隊(duì)列成功后,判斷當(dāng)前池內(nèi)線(xiàn)程數(shù)是否為0,如果是則創(chuàng)建一個(gè)firstTask為null的worker,這個(gè)worker會(huì)從等待隊(duì)列中獲取任務(wù)并執(zhí)行。            else if (workerCountOf(recheck) == 0)                addWorker(null, false);        }        else if (!addWorker(command, false))            reject(command);

線(xiàn)程池提交任務(wù)后,首先判斷當(dāng)前池中線(xiàn)程數(shù)是否小于corePoolSize。
如果小于則嘗試創(chuàng)建新的線(xiàn)程執(zhí)行該任務(wù);否則嘗試添加到等待隊(duì)列。
如果添加隊(duì)列成功,判斷當(dāng)前池內(nèi)線(xiàn)程數(shù)是否為0,如果是則創(chuàng)建一個(gè)firstTask為null的worker,這個(gè)worker會(huì)從等待隊(duì)列中獲取任務(wù)并執(zhí)行。
如果添加到等待隊(duì)列失敗,一般是隊(duì)列已滿(mǎn),才會(huì)再?lài)L試創(chuàng)建新的線(xiàn)程。
但在創(chuàng)建之前需要與maximumPoolSize比較,如果小于則創(chuàng)建成功。
否則執(zhí)行拒絕策略。

上述問(wèn)題需區(qū)分JDK版本。在1.6版本之后,如果corePoolSize=0,提交任務(wù)時(shí)如果線(xiàn)程池為空,則會(huì)立即創(chuàng)建一個(gè)線(xiàn)程來(lái)執(zhí)行任務(wù)(先排隊(duì)再獲?。?;如果提交任務(wù)的時(shí)候,線(xiàn)程池不為空,則先在等待隊(duì)列中排隊(duì),只有隊(duì)列滿(mǎn)了才會(huì)創(chuàng)建新線(xiàn)程。

所以,優(yōu)化在于,在隊(duì)列沒(méi)有滿(mǎn)的這段時(shí)間內(nèi),會(huì)有一個(gè)線(xiàn)程在消費(fèi)提交的任務(wù);1.6之前的實(shí)現(xiàn)是,必須等隊(duì)列滿(mǎn)了之后,才開(kāi)始消費(fèi)。

2 線(xiàn)程池創(chuàng)建之后,會(huì)立即創(chuàng)建核心線(xiàn)程么

之前有人問(wèn)過(guò)我這個(gè)問(wèn)題,因?yàn)樗l(fā)現(xiàn)應(yīng)用中有些Bean創(chuàng)建了線(xiàn)程池,但是這個(gè)Bean一般情況下用不到,所以咨詢(xún)我是否需要把這個(gè)線(xiàn)程池注釋掉,以減少應(yīng)用運(yùn)行時(shí)的線(xiàn)程數(shù)(該應(yīng)用運(yùn)行時(shí)線(xiàn)程過(guò)多。)

不會(huì)。從上面的源碼可以看出,在剛剛創(chuàng)建ThreadPoolExecutor的時(shí)候,線(xiàn)程并不會(huì)立即啟動(dòng),而是要等到有任務(wù)提交時(shí)才會(huì)啟動(dòng),除非調(diào)用了prestartCoreThread/prestartAllCoreThreads事先啟動(dòng)核心線(xiàn)程。

prestartCoreThread:Starts a core thread, causing it to idly wait for work. This overrides the default policy of starting core threads only when new tasks are executed.
prestartAllCoreThreads:Starts all core threads.

3 核心線(xiàn)程永遠(yuǎn)不會(huì)銷(xiāo)毀么

這個(gè)問(wèn)題有點(diǎn)tricky。首先我們要明確一下概念,雖然在JavaDoc中也使用了“core/non-core threads”這樣的描述,但其實(shí)這是一個(gè)動(dòng)態(tài)的概念,JDK并沒(méi)有給一部分線(xiàn)程打上“core”的標(biāo)記,做什么特殊化的處理。這個(gè)問(wèn)題我認(rèn)為想要探討的是閑置線(xiàn)程終結(jié)策略的問(wèn)題。

在JDK1.6之前,線(xiàn)程池會(huì)盡量保持corePoolSize個(gè)核心線(xiàn)程,即使這些線(xiàn)程閑置了很長(zhǎng)時(shí)間。這一點(diǎn)曾被開(kāi)發(fā)者詬病,所以從JDK1.6開(kāi)始,提供了方法allowsCoreThreadTimeOut,如果傳參為true,則允許閑置的核心線(xiàn)程被終止。

請(qǐng)注意這種策略和corePoolSize=0的區(qū)別。我總結(jié)的區(qū)別是:

corePoolSize=0:在一般情況下只使用一個(gè)線(xiàn)程消費(fèi)任務(wù),只有當(dāng)并發(fā)請(qǐng)求特別多、等待隊(duì)列都滿(mǎn)了之后,才開(kāi)始用多線(xiàn)程。
allowsCoreThreadTimeOut=true && corePoolSize>1:在一般情況下就開(kāi)始使用多線(xiàn)程(corePoolSize個(gè)),當(dāng)并發(fā)請(qǐng)求特別多,等待隊(duì)列都滿(mǎn)了之后,繼續(xù)加大線(xiàn)程數(shù)。但是當(dāng)請(qǐng)求沒(méi)有的時(shí)候,允許核心線(xiàn)程也終止。
所以corePoolSize=0的效果,基本等同于allowsCoreThreadTimeOut=true && corePoolSize=1,但實(shí)現(xiàn)細(xì)節(jié)其實(shí)不同。

在JDK1.6之后,如果allowsCoreThreadTimeOut=true,核心線(xiàn)程也可以被終止。

4 如何保證線(xiàn)程不被銷(xiāo)毀

首先我們要明確一下線(xiàn)程池模型。線(xiàn)程池有個(gè)內(nèi)部類(lèi)Worker,它實(shí)現(xiàn)了Runnable接口,首先,它自己要run起來(lái)。然后它會(huì)在合適的時(shí)候獲取我們提交的Runnable任務(wù),然后調(diào)用任務(wù)的run()接口。一個(gè)Worker不終止的話(huà)可以不斷執(zhí)行任務(wù)。

我們前面說(shuō)的“線(xiàn)程池中的線(xiàn)程”,其實(shí)就是Worker;等待隊(duì)列中的元素,是我們提交的Runnable任務(wù)。

每一個(gè)Worker在創(chuàng)建出來(lái)的時(shí)候,會(huì)調(diào)用它本身的run()方法,實(shí)現(xiàn)是runWorker(this),這個(gè)實(shí)現(xiàn)的核心是一個(gè)while循環(huán),這個(gè)循環(huán)不結(jié)束,Worker線(xiàn)程就不會(huì)終止,就是這個(gè)基本邏輯。

在這個(gè)while條件中,有個(gè)getTask()方法是核心中的核心,它所做的事情就是從等待隊(duì)列中取出任務(wù)來(lái)執(zhí)行:
如果沒(méi)有達(dá)到corePoolSize,則創(chuàng)建的Worker在執(zhí)行完它承接的任務(wù)后,會(huì)用workQueue.take()取任務(wù)、注意,這個(gè)接口是阻塞接口,如果取不到任務(wù),Worker線(xiàn)程一直阻塞。
如果超過(guò)了corePoolSize,或者allowCoreThreadTimeOut,一個(gè)Worker在空閑了之后,會(huì)用workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)取任務(wù)。注意,這個(gè)接口只阻塞等待keepAliveTime時(shí)間,超過(guò)這個(gè)時(shí)間返回null,則Worker的while循環(huán)執(zhí)行結(jié)束,則被終止了。

  1. final void runWorker(Worker w) {        Thread wt = Thread.currentThread();        Runnable task = w.firstTask;        w.firstTask = null;        w.unlock(); // allow interrupts        boolean completedAbruptly = true;        try {            // 看這里,核心邏輯在這里            while (task != null || (task = getTask()) != null) {                w.lock();                // If pool is stopping, ensure thread is interrupted;                // if not, ensure thread is not interrupted.  This                // requires a recheck in second case to deal with                // shutdownNow race while clearing interrupt                if ((runStateAtLeast(ctl.get(), STOP) ||                     (Thread.interrupted() &&                      runStateAtLeast(ctl.get(), STOP))) &&                    !wt.isInterrupted())                    wt.interrupt();                try {                    beforeExecute(wt, task);                    Throwable thrown = null;                    try {                        task.run();                    } catch (RuntimeException x) {                        thrown = x; throw x;                    } catch (Error x) {                        thrown = x; throw x;                    } catch (Throwable x) {                        thrown = x; throw new Error(x);                    } finally {                        afterExecute(task, thrown);                    }                } finally {                    task = null;                    w.completedTasks++;                    w.unlock();                }            }            completedAbruptly = false;        } finally {            processWorkerExit(w, completedAbruptly);        }    } 


private Runnable getTask() {        boolean timedOut = false; // Did the last poll() time out?        for (;;) {            int c = ctl.get();            int rs = runStateOf(c);            // Check if queue empty only if necessary.            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {                decrementWorkerCount();                return null;            }            int wc = workerCountOf(c);            // Are workers subject to culling?            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;            if ((wc > maximumPoolSize || (timed && timedOut))                && (wc > 1 || workQueue.isEmpty())) {                if (compareAndDecrementWorkerCount(c))                    return null;                continue;            }            try {                // 注意,核心中的核心在這里                Runnable r = timed ?                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :                    workQueue.take();                if (r != null)                    return r;                timedOut = true;            } catch (InterruptedException retry) {                timedOut = false;            }        }    }

實(shí)現(xiàn)方式非常巧妙,核心線(xiàn)程(Worker)即使一直空閑也不終止,是通過(guò)workQueue.take()實(shí)現(xiàn)的,它會(huì)一直阻塞到從等待隊(duì)列中取到新的任務(wù)。非核心線(xiàn)程空閑指定時(shí)間后終止是通過(guò)workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)實(shí)現(xiàn)的,一個(gè)空閑的Worker只等待keepAliveTime,如果還沒(méi)有取到任務(wù)則循環(huán)終止,線(xiàn)程也就運(yùn)行結(jié)束了。

引申思考

Worker本身就是個(gè)線(xiàn)程,它再調(diào)用我們傳入的Runnable.run(),會(huì)啟動(dòng)一個(gè)子線(xiàn)程么?如果你還沒(méi)有答案,再回想一下Runnable和Thread的關(guān)系。

5 空閑線(xiàn)程過(guò)多會(huì)有什么問(wèn)題

籠統(tǒng)地回答是會(huì)占用內(nèi)存,我們分析一下占用了哪些內(nèi)存。首先,比較普通的一部分,一個(gè)線(xiàn)程的內(nèi)存模型:

虛擬機(jī)棧
本地方法棧
程序計(jì)數(shù)器
我想額外強(qiáng)調(diào)是下面這幾個(gè)內(nèi)存占用,需要小心:

ThreadLocal:業(yè)務(wù)代碼是否使用了ThreadLocal?就算沒(méi)有,Spring框架中也大量使用了ThreadLocal,你所在公司的框架可能也是一樣。
局部變量:線(xiàn)程處于阻塞狀態(tài),肯定還有棧幀沒(méi)有出棧,棧幀中有局部變量表,凡是被局部變量表引用的內(nèi)存都不能回收。所以如果這個(gè)線(xiàn)程創(chuàng)建了比較大的局部變量,那么這一部分內(nèi)存無(wú)法GC。
TLAB機(jī)制:如果你的應(yīng)用線(xiàn)程數(shù)處于高位,那么新的線(xiàn)程初始化可能因?yàn)镋den沒(méi)有足夠的空間分配TLAB而觸發(fā)YoungGC。

線(xiàn)程池保持空閑的核心線(xiàn)程是它的默認(rèn)配置,一般來(lái)講是沒(méi)有問(wèn)題的,因?yàn)樗加玫膬?nèi)存一般不大。怕的就是業(yè)務(wù)代碼中使用ThreadLocal緩存的數(shù)據(jù)過(guò)大又不清理。

如果你的應(yīng)用線(xiàn)程數(shù)處于高位,那么需要觀察一下YoungGC的情況,估算一下Eden大小是否足夠。如果不夠的話(huà),可能要謹(jǐn)慎地創(chuàng)建新線(xiàn)程,并且讓空閑的線(xiàn)程終止;必要的時(shí)候,可能需要對(duì)JVM進(jìn)行調(diào)參。

6 keepAliveTime=0會(huì)怎么樣

這也是個(gè)爭(zhēng)議點(diǎn)。有的博文說(shuō)等于0表示空閑線(xiàn)程永遠(yuǎn)不會(huì)終止,有的說(shuō)表示執(zhí)行完立刻終止。還有的說(shuō)等于-1表示空閑線(xiàn)程永遠(yuǎn)不會(huì)終止。其實(shí)稍微看一下源碼知道了,這里我直接拋出答案。

在JDK1.8中,keepAliveTime=0表示非核心線(xiàn)程執(zhí)行完立刻終止。

默認(rèn)情況下,keepAliveTime小于0,初始化的時(shí)候才會(huì)報(bào)錯(cuò);但如果allowsCoreThreadTimeOut,keepAliveTime必須大于0,不然初始化報(bào)錯(cuò)。

7 怎么進(jìn)行異常處理

很多代碼的寫(xiě)法,我們都習(xí)慣按照常見(jiàn)范式去編寫(xiě),而沒(méi)有去思考為什么。比如:

如果我們使用execute()提交任務(wù),我們一般要在Runable任務(wù)的代碼加上try-catch進(jìn)行異常處理。
如果我們使用submit()提交任務(wù),我們一般要在主線(xiàn)程中,對(duì)Future.get()進(jìn)行try-catch進(jìn)行異常處理。
&mdash;&mdash; 但是在上面,我提到過(guò),submit()底層實(shí)現(xiàn)依賴(lài)execute(),兩者應(yīng)該統(tǒng)一呀,為什么有差異呢?下面再扒一扒submit()的源碼,它的實(shí)現(xiàn)蠻有意思。

首先,ThreadPoolExecutor中沒(méi)有submit的代碼,而是在它的父類(lèi)AbstractExecutorService中,有三個(gè)submit的重載方法,代碼非常簡(jiǎn)單,關(guān)鍵代碼就兩行:

public Future<?> submit(Runnable task) {        if (task == null) throw new NullPointerException();        RunnableFuture<Void> ftask = newTaskFor(task, null);        execute(ftask);        return ftask;    }    public <T> Future<T> submit(Runnable task, T result) {        if (task == null) throw new NullPointerException();        RunnableFuture<T> ftask = newTaskFor(task, result);        execute(ftask);        return ftask;    }    public <T> Future<T> submit(Callable<T> task) {        if (task == null) throw new NullPointerException();        RunnableFuture<T> ftask = newTaskFor(task);        execute(ftask);        return ftask;    }

正是因?yàn)檫@三個(gè)重載方法,都調(diào)用了execute,所以我才說(shuō)submit底層依賴(lài)execute。通過(guò)查看這里execute的實(shí)現(xiàn),我們不難發(fā)現(xiàn),它就是ThreadPoolExecutor中的實(shí)現(xiàn),所以,造成submit和execute的差異化的代碼,不在這。那么造成差異的一定在newTaskFor方法中。這個(gè)方法也就new了一個(gè)FutureTask而已,F(xiàn)utureTask實(shí)現(xiàn)RunnableFuture接口,RunnableFuture接口繼承Runnable接口和Future接口。而Callable只是FutureTask的一個(gè)成員變量。

所以講到這里,就有另一個(gè)Java基礎(chǔ)知識(shí)點(diǎn):Callable和Future的關(guān)系。我們一般用Callable編寫(xiě)任務(wù)代碼,F(xiàn)uture是異步返回對(duì)象,通過(guò)它的get方法,阻塞式地獲取結(jié)果。FutureTask的核心代碼就是實(shí)現(xiàn)了Future接口,也就是get方法的實(shí)現(xiàn):

public V get() throws InterruptedException, ExecutionException {        int s = state;        if (s <= COMPLETING)            // 核心代碼            s = awaitDone(false, 0L);        return report(s);    }    private int awaitDone(boolean timed, long nanos)        throws InterruptedException {        final long deadline = timed ? System.nanoTime() + nanos : 0L;        WaitNode q = null;        boolean queued = false;        // 死循環(huán)        for (;;) {            if (Thread.interrupted()) {                removeWaiter(q);                throw new InterruptedException();            }            int s = state;            // 只有任務(wù)的狀態(tài)是&rsquo;已完成&lsquo;,才會(huì)跳出死循環(huán)            if (s > COMPLETING) {                if (q != null)                    q.thread = null;                return s;            }            else if (s == COMPLETING) // cannot time out yet                Thread.yield();            else if (q == null)                q = new WaitNode();            else if (!queued)                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,                                                     q.next = waiters, q);            else if (timed) {                nanos = deadline - System.nanoTime();                if (nanos <= 0L) {                    removeWaiter(q);                    return state;                }                LockSupport.parkNanos(this, nanos);            }            else                LockSupport.park(this);        }    }

get的核心實(shí)現(xiàn)是有個(gè)awaitDone方法,這是一個(gè)死循環(huán),只有任務(wù)的狀態(tài)是“已完成”,才會(huì)跳出死循環(huán);否則會(huì)依賴(lài)UNSAFE包下的LockSupport.park原語(yǔ)進(jìn)行阻塞,等待LockSupport.unpark信號(hào)量。而這個(gè)信號(hào)量只有當(dāng)運(yùn)行結(jié)束獲得結(jié)果、或者出現(xiàn)異常的情況下,才會(huì)發(fā)出來(lái)。分別對(duì)應(yīng)方法set和setException。這就是異步執(zhí)行、阻塞獲取的原理,扯得有點(diǎn)遠(yuǎn)了。

回到最初我們的疑問(wèn),為什么submit之后,通過(guò)get方法可以獲取到異常?原因是FutureTask有一個(gè)Object類(lèi)型的outcome成員變量,用來(lái)記錄執(zhí)行結(jié)果。這個(gè)結(jié)果可以是傳入的泛型,也可以是Throwable異常:

public void run() {        if (state != NEW ||            !UNSAFE.compareAndSwapObject(this, runnerOffset,                                         null, Thread.currentThread()))            return;        try {            Callable<V> c = callable;            if (c != null && state == NEW) {                V result;                boolean ran;                try {                    result = c.call();                    ran = true;                } catch (Throwable ex) {                    result = null;                    ran = false;                    setException(ex);                }                if (ran)                    set(result);            }        } finally {            // runner must be non-null until state is settled to            // prevent concurrent calls to run()            runner = null;            // state must be re-read after nulling runner to prevent            // leaked interrupts            int s = state;            if (s >= INTERRUPTING)                handlePossibleCancellationInterrupt(s);        }    }  // get方法中依賴(lài)的,報(bào)告執(zhí)行結(jié)果    private V report(int s) throws ExecutionException {        Object x = outcome;        if (s == NORMAL)            return (V)x;        if (s >= CANCELLED)            throw new CancellationException();        throw new ExecutionException((Throwable)x);    }

FutureTask的另一個(gè)巧妙的地方就是借用RunnableAdapter內(nèi)部類(lèi),將submit的Runnable封裝成Callable。所以就算你submit的是Runnable,一樣可以用get獲取到異常。

不論是用execute還是submit,都可以自己在業(yè)務(wù)代碼上加try-catch進(jìn)行異常處理。我一般喜歡使用這種方式,因?yàn)槲蚁矚g對(duì)不同業(yè)務(wù)場(chǎng)景的異常進(jìn)行差異化處理,至少打不一樣的日志吧。
如果是execute,還可以自定義線(xiàn)程池,繼承ThreadPoolExecutor并復(fù)寫(xiě)其afterExecute(Runnable r, Throwable t)方法。
或者實(shí)現(xiàn)Thread.UncaughtExceptionHandler接口,實(shí)現(xiàn)void uncaughtException(Thread t, Throwable e);方法,并將該handler傳遞給線(xiàn)程池的ThreadFactory。
但是注意,afterExecute和UncaughtExceptionHandler都不適用submit。因?yàn)橥ㄟ^(guò)上面的FutureTask.run()不難發(fā)現(xiàn),它自己對(duì)Throwable進(jìn)行了try-catch,封裝到了outcome屬性,所以底層方法execute的Worker是拿不到異常信息的。

8 線(xiàn)程池需不需要關(guān)閉

一般來(lái)講,線(xiàn)程池的生命周期跟隨服務(wù)的生命周期。如果一個(gè)服務(wù)(Service)停止服務(wù)了,那么需要調(diào)用shutdown方法進(jìn)行關(guān)閉。所以ExecutorService.shutdown在Java以及一些中間件的源碼中,是封裝在Service的shutdown方法內(nèi)的。

如果是Server端不重啟就不停止提供服務(wù),我認(rèn)為是不需要特殊處理的。

9 shutdown和shutdownNow的區(qū)別

shutdown => 平緩關(guān)閉,等待所有已添加到線(xiàn)程池中的任務(wù)執(zhí)行完再關(guān)閉。
shutdownNow => 立刻關(guān)閉,停止正在執(zhí)行的任務(wù),并返回隊(duì)列中未執(zhí)行的任務(wù)。
本來(lái)想分析一下兩者的源碼的,但是發(fā)現(xiàn)本文的篇幅已經(jīng)過(guò)長(zhǎng)了,源碼也貼了不少。感興趣的朋友自己看一下即可。

10 Spring中有哪些和ThreadPoolExecutor類(lèi)似的工具

關(guān)于線(xiàn)程池的十個(gè)熱門(mén)問(wèn)題

這里我想著重強(qiáng)調(diào)的就是SimpleAsyncTaskExecutor,Spring中使用的@Async注解,底層就是基于SimpleAsyncTaskExecutor去執(zhí)行任務(wù),只不過(guò)它不是線(xiàn)程池,而是每次都新開(kāi)一個(gè)線(xiàn)程。

另外想要強(qiáng)調(diào)的是Executor接口。Java初學(xué)者容易想當(dāng)然的以為Executor結(jié)尾的類(lèi)就是一個(gè)線(xiàn)程池,而上面的都是反例。我們可以在JDK的execute方法上看到這個(gè)注釋?zhuān)?/p>

/*** Executes the given command at some time in the future.  The command* may execute in a new thread, in a pooled thread, or in the calling* thread, at the discretion of the {@code Executor} implementation.*/

所以,它的職責(zé)并不是提供一個(gè)線(xiàn)程池的接口,而是提供一個(gè)“將來(lái)執(zhí)行命令”的接口。真正能代表線(xiàn)程池意義的,是ThreadPoolExecutor類(lèi),而不是Executor接口。

最佳實(shí)踐總結(jié)

【強(qiáng)制】使用ThreadPoolExecutor的構(gòu)造函數(shù)聲明線(xiàn)程池,避免使用Executors類(lèi)的 newFixedThreadPool和newCachedThreadPool。
【強(qiáng)制】 創(chuàng)建線(xiàn)程或線(xiàn)程池時(shí)請(qǐng)指定有意義的線(xiàn)程名稱(chēng),方便出錯(cuò)時(shí)回溯。即threadFactory參數(shù)要構(gòu)造好。
【建議】建議不同類(lèi)別的業(yè)務(wù)用不同的線(xiàn)程池。
【建議】CPU密集型任務(wù)(N+1):這種任務(wù)消耗的主要是CPU資源,可以將線(xiàn)程數(shù)設(shè)置為N(CPU核心數(shù))+1,比CPU核心數(shù)多出來(lái)的一個(gè)線(xiàn)程是為了防止線(xiàn)程偶發(fā)的缺頁(yè)中斷,或者其它原因?qū)е碌娜蝿?wù)暫停而帶來(lái)的影響。一旦任務(wù)暫停,CPU就會(huì)處于空閑狀態(tài),而在這種情況下多出來(lái)的一個(gè)線(xiàn)程就可以充分利用CPU的空閑時(shí)間。
【建議】I/O密集型任務(wù)(2N):這種任務(wù)應(yīng)用起來(lái),系統(tǒng)會(huì)用大部分的時(shí)間來(lái)處理I/O交互,而線(xiàn)程在處理I/O的時(shí)間段內(nèi)不會(huì)占用CPU來(lái)處理,這時(shí)就可以將CPU交出給其它線(xiàn)程使用。因此在I/O密集型任務(wù)的應(yīng)用中,我們可以多配置一些線(xiàn)程,具體的計(jì)算方法是2N。
【建議】workQueue不要使用無(wú)界隊(duì)列,盡量使用有界隊(duì)列。避免大量任務(wù)等待,造成OOM。
【建議】如果是資源緊張的應(yīng)用,使用allowsCoreThreadTimeOut可以提高資源利用率。
【建議】雖然使用線(xiàn)程池有多種異常處理的方式,但在任務(wù)代碼中,使用try-catch最通用,也能給不同任務(wù)的異常處理做精細(xì)化。
【建議】對(duì)于資源緊張的應(yīng)用,如果擔(dān)心線(xiàn)程池資源使用不當(dāng),可以利用ThreadPoolExecutor的API實(shí)現(xiàn)簡(jiǎn)單的監(jiān)控,然后進(jìn)行分析和優(yōu)化。

關(guān)于線(xiàn)程池的十個(gè)熱門(mén)問(wèn)題

線(xiàn)程池初始化示例:

private static final ThreadPoolExecutor pool;    static {        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("po-detail-pool-%d").build();        pool = new ThreadPoolExecutor(4, 8, 60L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(512),            threadFactory, new ThreadPoolExecutor.AbortPolicy());        pool.allowCoreThreadTimeOut(true);    }

threadFactory:給出帶業(yè)務(wù)語(yǔ)義的線(xiàn)程命名。
corePoolSize:快速啟動(dòng)4個(gè)線(xiàn)程處理該業(yè)務(wù),是足夠的。
maximumPoolSize:IO密集型業(yè)務(wù),我的服務(wù)器是4C8G的,所以4*2=8。
keepAliveTime:服務(wù)器資源緊張,讓空閑的線(xiàn)程快速釋放。
pool.allowCoreThreadTimeOut(true):也是為了在可以的時(shí)候,讓線(xiàn)程釋放,釋放資源。
workQueue:一個(gè)任務(wù)的執(zhí)行時(shí)長(zhǎng)在100~300ms,業(yè)務(wù)高峰期8個(gè)線(xiàn)程,按照10s超時(shí)(已經(jīng)很高了)。10s鐘,8個(gè)線(xiàn)程,可以處理10 1000ms / 200ms 8 = 400個(gè)任務(wù)左右,往上再取一點(diǎn),512已經(jīng)很多了。
handler:極端情況下,一些任務(wù)只能丟棄,保護(hù)服務(wù)端。

到此,關(guān)于“關(guān)于線(xiàn)程池的十個(gè)熱門(mén)問(wèn)題”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

當(dāng)前標(biāo)題:關(guān)于線(xiàn)程池的十個(gè)熱門(mén)問(wèn)題
標(biāo)題網(wǎng)址:http://www.rwnh.cn/article36/igjjpg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供做網(wǎng)站、服務(wù)器托管、軟件開(kāi)發(fā)、App設(shè)計(jì)、ChatGPT微信小程序

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站網(wǎng)頁(yè)設(shè)計(jì)
黑龙江省| 舟山市| 汽车| 淮阳县| 交口县| 鄂州市| 松桃| 武穴市| 商城县| 合川市| 襄垣县| 兴仁县| 清原| 博客| 如皋市| 天台县| 时尚| 商南县| 昆明市| 松滋市| 谢通门县| 如皋市| 赣州市| 正蓝旗| 广安市| 光山县| 枣强县| 江永县| 阳谷县| 长葛市| 定西市| 胶南市| 库车县| 尚义县| 名山县| 东乡县| 万源市| 武城县| 兴山县| 横山县| 怀化市|