Java中該如何優(yōu)雅的使用線程池詳解
為什么要用線程池?
線程是不是越多越好?
- 線程在java中是一個對象,更是操作系統(tǒng)的資源,線程創(chuàng)建、銷毀需要時間。如果創(chuàng)建時間+小會時間>執(zhí)行任務(wù)時間就很不合算。
- java對象占用堆內(nèi)存,操作系統(tǒng)線程占用系統(tǒng)內(nèi)存,根據(jù)jvm規(guī)范,一個線程默認(rèn)最大棧大小1M,這個??臻g是需要從系統(tǒng)內(nèi)存中分配的。線程過多,會消耗很多的內(nèi)存。
- 操作系統(tǒng)需要頻繁切換線程上下文(每個線都想被運(yùn)行),影響性能。
線程池的推出,就是為了方便邊的控制線程數(shù)量。
線程池
線程池基本概念
線程池包括以下四個基本組成部分:
- 線程池管理器:用于創(chuàng)建并管理線程池,包括創(chuàng)建線程池,銷毀線程池,添加新任務(wù);
- 工作線程:線程池中線程,在沒有任務(wù)時處于等待狀態(tài),可以循環(huán)的執(zhí)行任務(wù);
- 任務(wù)接口:每個任務(wù)必須實現(xiàn)的借口,以供工作線程調(diào)度任務(wù)的執(zhí)行,它主要規(guī)定了任務(wù)的入口,任務(wù)執(zhí)行完后的收尾工作,任務(wù)的執(zhí)行狀態(tài)等;
- 任務(wù)隊列:用于存放沒有處理的任務(wù)。提供一種緩沖機(jī)制。
線程池接口定義和實現(xiàn)類
可以認(rèn)為ScheduledThreadPoolExector是最豐富的實現(xiàn)類。
ExecutorService
public interface ExecutorService extends Executor { /** * 優(yōu)雅關(guān)閉線程池,之前提交的任務(wù)將被執(zhí)行,但是不會接受新的任務(wù)。 */ void shutdown(); /** * 嘗試停止所有正在執(zhí)行的任務(wù),停止等待任務(wù)的處理,并返回等待執(zhí)行任務(wù)的列表。 */ List<Runnable> shutdownNow(); /** * 如果此線程池已關(guān)閉,則返回true. */ boolean isShutdown(); /** * 如果關(guān)閉后的所有任務(wù)都已完成,則返回true */ boolean isTerminated(); /** * 監(jiān)測ExecutorService是否已經(jīng)關(guān)閉,直到所有任務(wù)完成執(zhí)行,或超時發(fā)生,或當(dāng)前線程被中斷。 */ boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; /** * 提交一個用于執(zhí)行的Callable返回任務(wù),并返回一個Future,用于獲取Callable執(zhí)行結(jié)果。 */ <T> Future<T> submit(Callable<T> task); /** * 提交可運(yùn)行任務(wù)以執(zhí)行,并返回Future,執(zhí)行結(jié)果為傳入的result */ <T> Future<T> submit(Runnable task, T result); /** * 提交可運(yùn)行任務(wù)以執(zhí)行,并返回Future對象,執(zhí)行結(jié)果為null */ Future<?> submit(Runnable task); /** * 執(zhí)行給定的任務(wù)集合,執(zhí)行完畢后,則返回結(jié)果。 */ <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; /** * 執(zhí)行給定的任務(wù)集合,執(zhí)行完畢或者超時后,則返回結(jié)果,其他任務(wù)終止。 */ <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; /** * 執(zhí)行給定的任務(wù),任意一個執(zhí)行成功則返回結(jié)果,其他任務(wù)終止。 */ <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; /** * 執(zhí)行給定的任務(wù),任意一個執(zhí)行成功或者超時后,則返回結(jié)果,其他任務(wù)終止 */ <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException; }
ScheduledExecutorService
public interface ScheduledExecutorService extends ExecutorService { /** * 創(chuàng)建并執(zhí)行一個一次性任務(wù),過了延遲時間就會被執(zhí)行 */ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit); /** * 創(chuàng)建并執(zhí)行一個一次性任務(wù),過了延遲時間就會被執(zhí)行 */ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit); /** * 創(chuàng)建并執(zhí)行一個周期性任務(wù),過了給定的初始化延遲時間,會第一次被執(zhí)行。執(zhí)行過程中發(fā)生了異常,那么任務(wù)停止 * 一次任務(wù)執(zhí)行時長超過了周期時間,下一次任務(wù)會等到該次任務(wù)執(zhí)行結(jié)束后,立刻執(zhí)行,這也是它和scheduleWithTixedDelay的重要區(qū)別 */ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); /** * 創(chuàng)建并執(zhí)行一個周期性任務(wù),過了給定的初始化延遲時間,會第一次被執(zhí)行。執(zhí)行過程中發(fā)生了異常,那么任務(wù)停止 * 一次任務(wù)執(zhí)行時長超過了周期時間,下一次任務(wù)會在該次任務(wù)執(zhí)行結(jié)束的時間基礎(chǔ)上,計算執(zhí)行延時。 * 對于超時周期的長時間處理任務(wù)的不同處理方式,這是它和scheduleAtFixedRate的重要區(qū)別 */ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); }
線程池工具類
在使用過程中,可以自己實例化線程池,也可以用Executors創(chuàng)建線程池的工廠累,常用方法如下:
newFixedThreadPool(int nThreads)
創(chuàng)建一個固定大小、任務(wù)隊列容量誤解的線程池。核心線程數(shù)=最大線程數(shù)。
newCachedThreadPool()
創(chuàng)建的是一個大小無界的緩沖線程池。它的任務(wù)隊列是一個同步隊列。任務(wù)加入到池中,如果池中有空閑線程,則用空閑線程執(zhí)行,如無則創(chuàng)建新線程執(zhí)行。池中的線程空閑時間超過60秒,將被銷毀釋放。線程數(shù)隨任務(wù)的多少變化。適用于執(zhí)行耗時較小的異步任務(wù)。池的核心線程數(shù)=0,最大線程=Integer.MAX_VALUE
newSingleThreadExecutor()
只有一個線程來執(zhí)行無界任務(wù)隊列的單一線程池。該線程池確保任務(wù)加入的順序一個一個一次執(zhí)行。當(dāng)唯一的線程因任務(wù)異常中止時,將創(chuàng)建一個新的線程來繼續(xù)執(zhí)行后續(xù)的任務(wù)。與newFixedThreadPool(1)的區(qū)別在于,單一線程池的池大小在newSingleThreadExecutor方法中硬編碼,不能再改變的。
newScheduledThreadPool(int corePoolSize)
能定時執(zhí)行任務(wù)的線程池。該池的核心線程數(shù)由參數(shù)指定,最大線程數(shù)=Integer.MAX_VALUE
任務(wù)線程池執(zhí)行過程
如何確認(rèn)合適的線程數(shù)量?
- 如果是CPU密集型應(yīng)用,則線程池大小設(shè)置為N+1 (N為CPU總核數(shù))
- 如果是IO密集型應(yīng)用,則線程池大小設(shè)置為2N+1 (N為CPU總核數(shù))
- 線程等待時間(IO)所占比例越高,需要越多線程。
- 線程CPU時間所占比例越高,需要越少線程。
一個系統(tǒng)最快的部分是CPU,所以決定一個系統(tǒng)吞吐量上限的是CPU。增強(qiáng)CPU處理能力,可以提高系統(tǒng)吞吐量上限。但根據(jù)短板效應(yīng),真實的系統(tǒng)吞吐量并不能單純根據(jù)CPU來計算。那要提高系統(tǒng)吞吐量,就需要從“系統(tǒng)短板”(比如網(wǎng)絡(luò)延遲、IO)著手:
- 盡量提高短板操作的并行化比率,比如多線程下載技術(shù);
- 增強(qiáng)短板能力,比如用NIO替代IO;
線程池的使用分析
public class ExecutorsUse { /** * 測試: 提交15 個執(zhí)行時間需要3秒的任務(wù),看線程池的狀況 * * @param threadPoolExecutor 傳入不同的線程池,看不同的結(jié)果 * @throws Exception */ public void testCommon(ThreadPoolExecutor threadPoolExecutor) throws Exception { // 測試: 提交15個執(zhí)行時間需要3秒的任務(wù),看超過大小的2個,對應(yīng)的處理情況 for (int i = 0; i < 15; i++) { int n = i; threadPoolExecutor.submit(() -> { try { System.out.println("開始執(zhí)行:" + n); Thread.sleep(3000L); System.err.println("執(zhí)行結(jié)束:" + n); } catch (InterruptedException e) { e.printStackTrace(); } } ); System.out.println("任務(wù)提交成功 :" + i); } // 查看線程數(shù)量,查看隊列等待數(shù)量 Thread.sleep(500L); System.out.println("當(dāng)前線程池線程數(shù)量為:" + threadPoolExecutor.getPoolSize()); System.out.println("當(dāng)前線程池等待的數(shù)量為:" + threadPoolExecutor.getQueue().size()); // 等待15秒,查看線程數(shù)量和隊列數(shù)量(理論上,會被超出核心線程數(shù)量的線程自動銷毀) Thread.sleep(15000L); System.out.println("當(dāng)前線程池線程數(shù)量為:" + threadPoolExecutor.getPoolSize()); System.out.println("當(dāng)前線程池等待的數(shù)量為:" + threadPoolExecutor.getQueue().size()); } /** * 1、線程池信息: 核心線程數(shù)量5,最大數(shù)量10,無界隊列,超出核心線程數(shù)量的線程存活時間:5秒, 指定拒絕策略 * * @throws Exception */ private void threadPoolExecutorTest1() throws Exception { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); testCommon(threadPoolExecutor); // 預(yù)計結(jié)果:線程池線程數(shù)量為:5,超出數(shù)量的任務(wù),其他的進(jìn)入隊列中等待被執(zhí)行 } /** * 2、 線程池信息: 核心線程數(shù)量5,最大數(shù)量10,隊列大小3,超出核心線程數(shù)量的線程存活時間:5秒, 指定拒絕策略的 * * @throws Exception */ private void threadPoolExecutorTest2() throws Exception { // 創(chuàng)建一個 核心線程數(shù)量為5,最大數(shù)量為10,等待隊列最大是3 的線程池,也就是最大容納13個任務(wù)。 // 默認(rèn)的策略是拋出RejectedExecutionException異常,java.util.concurrent.ThreadPoolExecutor.AbortPolicy ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.err.println("有任務(wù)被拒絕執(zhí)行了"); } }); testCommon(threadPoolExecutor); // 預(yù)計結(jié)果: // 1、 5個任務(wù)直接分配線程開始執(zhí)行 // 2、 3個任務(wù)進(jìn)入等待隊列 // 3、 隊列不夠用,臨時加開5個線程來執(zhí)行任務(wù)(5秒沒活干就銷毀) // 4、 隊列和線程池都滿了,剩下2個任務(wù),沒資源了,被拒絕執(zhí)行。 // 5、 任務(wù)執(zhí)行,5秒后,如果無任務(wù)可執(zhí)行,銷毀臨時創(chuàng)建的5個線程 } /** * 3、 線程池信息: 核心線程數(shù)量5,最大數(shù)量5,無界隊列,超出核心線程數(shù)量的線程存活時間:5秒 * * @throws Exception */ private void threadPoolExecutorTest3() throws Exception { // 和Executors.newFixedThreadPool(int nThreads)一樣的 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); testCommon(threadPoolExecutor); // 預(yù)計結(jié):線程池線程數(shù)量為:5,超出數(shù)量的任務(wù),其他的進(jìn)入隊列中等待被執(zhí)行 } /** * 4、 線程池信息: * 核心線程數(shù)量0,最大數(shù)量Integer.MAX_VALUE,SynchronousQueue隊列,超出核心線程數(shù)量的線程存活時間:60秒 * * @throws Exception */ private void threadPoolExecutorTest4() throws Exception { // SynchronousQueue,實際上它不是一個真正的隊列,因為它不會為隊列中元素維護(hù)存儲空間。與其他隊列不同的是,它維護(hù)一組線程,這些線程在等待著把元素加入或移出隊列。 // 在使用SynchronousQueue作為工作隊列的前提下,客戶端代碼向線程池提交任務(wù)時, // 而線程池中又沒有空閑的線程能夠從SynchronousQueue隊列實例中取一個任務(wù), // 那么相應(yīng)的offer方法調(diào)用就會失?。慈蝿?wù)沒有被存入工作隊列)。 // 此時,ThreadPoolExecutor會新建一個新的工作者線程用于對這個入隊列失敗的任務(wù)進(jìn)行處理(假設(shè)此時線程池的大小還未達(dá)到其最大線程池大小maximumPoolSize)。 // 和Executors.newCachedThreadPool()一樣的 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); testCommon(threadPoolExecutor); // 預(yù)計結(jié)果: // 1、 線程池線程數(shù)量為:15,超出數(shù)量的任務(wù),其他的進(jìn)入隊列中等待被執(zhí)行 // 2、 所有任務(wù)執(zhí)行結(jié)束,60秒后,如果無任務(wù)可執(zhí)行,所有線程全部被銷毀,池的大小恢復(fù)為0 Thread.sleep(60000L); System.out.println("60秒后,再看線程池中的數(shù)量:" + threadPoolExecutor.getPoolSize()); } /** * 5、 定時執(zhí)行線程池信息:3秒后執(zhí)行,一次性任務(wù),到點(diǎn)就執(zhí)行 <br/> * 核心線程數(shù)量5,最大數(shù)量Integer.MAX_VALUE,DelayedWorkQueue延時隊列,超出核心線程數(shù)量的線程存活時間:0秒 * * @throws Exception */ private void threadPoolExecutorTest5() throws Exception { // 和Executors.newScheduledThreadPool()一樣的 ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5); threadPoolExecutor.schedule(new Runnable() { @Override public void run() { System.out.println("任務(wù)被執(zhí)行,現(xiàn)在時間:" + System.currentTimeMillis()); } }, 3000, TimeUnit.MILLISECONDS); System.out.println( "定時任務(wù),提交成功,時間是:" + System.currentTimeMillis() + ", 當(dāng)前線程池中線程數(shù)量:" + threadPoolExecutor.getPoolSize()); // 預(yù)計結(jié)果:任務(wù)在3秒后被執(zhí)行一次 } /** * 6、 定時執(zhí)行線程池信息:線程固定數(shù)量5 ,<br/> * 核心線程數(shù)量5,最大數(shù)量Integer.MAX_VALUE,DelayedWorkQueue延時隊列,超出核心線程數(shù)量的線程存活時間:0秒 * * @throws Exception */ private void threadPoolExecutorTest6() throws Exception { ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5); // 周期性執(zhí)行某一個任務(wù),線程池提供了兩種調(diào)度方式,這里單獨(dú)演示一下。測試場景一樣。 // 測試場景:提交的任務(wù)需要3秒才能執(zhí)行完畢??磧煞N不同調(diào)度方式的區(qū)別 // 效果1: 提交后,2秒后開始第一次執(zhí)行,之后每間隔1秒,固定執(zhí)行一次(如果發(fā)現(xiàn)上次執(zhí)行還未完畢,則等待完畢,完畢后立刻執(zhí)行)。 // 也就是說這個代碼中是,3秒鐘執(zhí)行一次(計算方式:每次執(zhí)行三秒,間隔時間1秒,執(zhí)行結(jié)束后馬上開始下一次執(zhí)行,無需等待) threadPoolExecutor.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("任務(wù)-1 被執(zhí)行,現(xiàn)在時間:" + System.currentTimeMillis()); } }, 2000, 1000, TimeUnit.MILLISECONDS); // 效果2:提交后,2秒后開始第一次執(zhí)行,之后每間隔1秒,固定執(zhí)行一次(如果發(fā)現(xiàn)上次執(zhí)行還未完畢,則等待完畢,等上一次執(zhí)行完畢后再開始計時,等待1秒)。 // 也就是說這個代碼鐘的效果看到的是:4秒執(zhí)行一次。 (計算方式:每次執(zhí)行3秒,間隔時間1秒,執(zhí)行完以后再等待1秒,所以是 3+1) threadPoolExecutor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("任務(wù)-2 被執(zhí)行,現(xiàn)在時間:" + System.currentTimeMillis()); } }, 2000, 1000, TimeUnit.MILLISECONDS); } /** * 7、 終止線程:線程池信息: 核心線程數(shù)量5,最大數(shù)量10,隊列大小3,超出核心線程數(shù)量的線程存活時間:5秒, 指定拒絕策略的 * * @throws Exception */ private void threadPoolExecutorTest7() throws Exception { // 創(chuàng)建一個 核心線程數(shù)量為5,最大數(shù)量為10,等待隊列最大是3 的線程池,也就是最大容納13個任務(wù)。 // 默認(rèn)的策略是拋出RejectedExecutionException異常,java.util.concurrent.ThreadPoolExecutor.AbortPolicy ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.err.println("有任務(wù)被拒絕執(zhí)行了"); } }); // 測試: 提交15個執(zhí)行時間需要3秒的任務(wù),看超過大小的2個,對應(yīng)的處理情況 for (int i = 0; i < 15; i++) { int n = i; threadPoolExecutor.submit(new Runnable() { @Override public void run() { try { System.out.println("開始執(zhí)行:" + n); Thread.sleep(3000L); System.err.println("執(zhí)行結(jié)束:" + n); } catch (InterruptedException e) { System.out.println("異常:" + e.getMessage()); } } }); System.out.println("任務(wù)提交成功 :" + i); } // 1秒后終止線程池 Thread.sleep(1000L); threadPoolExecutor.shutdown(); // 再次提交提示失敗 threadPoolExecutor.submit(new Runnable() { @Override public void run() { System.out.println("追加一個任務(wù)"); } }); // 結(jié)果分析 // 1、 10個任務(wù)被執(zhí)行,3個任務(wù)進(jìn)入隊列等待,2個任務(wù)被拒絕執(zhí)行 // 2、調(diào)用shutdown后,不接收新的任務(wù),等待13任務(wù)執(zhí)行結(jié)束 // 3、 追加的任務(wù)在線程池關(guān)閉后,無法再提交,會被拒絕執(zhí)行 } /** * 8、 立刻終止線程:線程池信息: 核心線程數(shù)量5,最大數(shù)量10,隊列大小3,超出核心線程數(shù)量的線程存活時間:5秒, 指定拒絕策略的 * * @throws Exception */ private void threadPoolExecutorTest8() throws Exception { // 創(chuàng)建一個 核心線程數(shù)量為5,最大數(shù)量為10,等待隊列最大是3 的線程池,也就是最大容納13個任務(wù)。 // 默認(rèn)的策略是拋出RejectedExecutionException異常,java.util.concurrent.ThreadPoolExecutor.AbortPolicy ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.err.println("有任務(wù)被拒絕執(zhí)行了"); } }); // 測試: 提交15個執(zhí)行時間需要3秒的任務(wù),看超過大小的2個,對應(yīng)的處理情況 for (int i = 0; i < 15; i++) { int n = i; threadPoolExecutor.submit(new Runnable() { @Override public void run() { try { System.out.println("開始執(zhí)行:" + n); Thread.sleep(3000L); System.err.println("執(zhí)行結(jié)束:" + n); } catch (InterruptedException e) { System.out.println("異常:" + e.getMessage()); } } }); System.out.println("任務(wù)提交成功 :" + i); } // 1秒后終止線程池 Thread.sleep(1000L); List<Runnable> shutdownNow = threadPoolExecutor.shutdownNow(); // 再次提交提示失敗 threadPoolExecutor.submit(new Runnable() { @Override public void run() { System.out.println("追加一個任務(wù)"); } }); System.out.println("未結(jié)束的任務(wù)有:" + shutdownNow.size()); // 結(jié)果分析 // 1、 10個任務(wù)被執(zhí)行,3個任務(wù)進(jìn)入隊列等待,2個任務(wù)被拒絕執(zhí)行 // 2、調(diào)用shutdownnow后,隊列中的3個線程不再執(zhí)行,10個線程被終止 // 3、 追加的任務(wù)在線程池關(guān)閉后,無法再提交,會被拒絕執(zhí)行 } public static void main(String[] args) throws Exception { // new ExecutorsUse().threadPoolExecutorTest1(); // new ExecutorsUse().threadPoolExecutorTest2(); // new ExecutorsUse().threadPoolExecutorTest3(); new ExecutorsUse().threadPoolExecutorTest4(); // new ExecutorsUse().threadPoolExecutorTest5(); // new ExecutorsUse().threadPoolExecutorTest6(); // new ExecutorsUse().threadPoolExecutorTest7(); // new ExecutorsUse().threadPoolExecutorTest8(); } }
合理配置線程池大小
如果想合理設(shè)置線程池的線程數(shù)量需要考慮兩個問題:
1、需要分析線程池執(zhí)行的任務(wù)的特性: CPU 密集型還是 IO 密集型。
2、每個任務(wù)執(zhí)行的平均時長大概是多少,這個任務(wù)的執(zhí)行時長可能還跟任務(wù)處理邏輯是否涉及到網(wǎng)絡(luò)傳輸以及底層系統(tǒng)資源依賴有關(guān)系。
如果是 CPU 密集型,那線程池的最大線程數(shù)可以配置為 cpu 核心數(shù)+1;如果是 IO 密集型,線程池設(shè)定最佳線程數(shù)目 = ((線程池設(shè)定的線程等待時間+線程 CPU 時間)/線程 CPU 時間 )* CPU 數(shù)目。
線程池的關(guān)閉
ThreadPoolExecutor 提供了兩個方法 ,用于線程池的關(guān)閉 ,分 別 是 shutdown() 和shutdownNow(),其中:shutdown():不會立即終止線程池,而是要等所有任務(wù)緩存隊列中的任務(wù)都執(zhí)行完后才終止,但再也不會接受新的任務(wù) shutdownNow():立即終止線程池,并嘗試打斷正在執(zhí)行的任務(wù),并且清空任務(wù)緩存隊列,返回尚未執(zhí)行的任務(wù)。
總結(jié)
到此這篇關(guān)于Java中該如何優(yōu)雅的使用線程池的文章就介紹到這了,更多相關(guān)Java優(yōu)雅使用線程池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于springboot使用rocketmq?RocketMQMessageListener參數(shù)問題
這篇文章主要介紹了springboot使用rocketmq?RocketMQMessageListener參數(shù)問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值需要的朋友可以參考下2022-11-11Java陷阱之a(chǎn)ssert關(guān)鍵字詳解
這篇文章詳細(xì)介紹了Java陷阱之a(chǎn)ssert關(guān)鍵字,有需要的朋友可以參考一下2013-09-09- 數(shù)組和二維數(shù)組感覺用王者榮耀的裝備欄來舉例解釋,應(yīng)該更易懂一些。從基礎(chǔ)開始講,后續(xù)會講到JAVA高級,中間會穿插面試題和項目實戰(zhàn),希望能給大家?guī)韼椭?/div> 2022-03-03
Java classloader和namespace詳細(xì)介紹
這篇文章主要介紹了Java classloader和namespace詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2017-03-03最新評論