java中线程池的几种实现方式
1、線程池簡介:
??? 多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。????
??? 假設一個服務器完成一項任務所需時間為:T1 創建線程時間,T2 在線程中執行任務的時間,T3 銷毀線程時間。
??? 如果:T1 + T3 遠大于 T2,則可以采用線程池,以提高服務器性能。
??????????????? 一個線程池包括以下四個基本組成部分:
??????????????? 1、線程池管理器(ThreadPool):用于創建并管理線程池,包括 創建線程池,銷毀線程池,添加新任務;
??????????????? 2、工作線程(PoolWorker):線程池中線程,在沒有任務時處于等待狀態,可以循環的執行任務;
??????????????? 3、任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完后的收尾工作,任務的執行狀態等;
??????????????? 4、任務隊列(taskQueue):用于存放沒有處理的任務。提供一種緩沖機制。
????????????????
??? 線程池技術正是關注如何縮短或調整T1,T3時間的技術,從而提高服務器程序性能的。它把T1,T3分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T3的開銷了。
??? 線程池不僅調整T1,T3產生的時間段,而且它還顯著減少了創建線程的數目,看一個例子:
??? 假設一個服務器一天要處理50000個請求,并且每個請求需要一個單獨的線程完成。在線程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目,而如果服務器不利用線程池來處理這些請求則線程總數為50000。一般線程池大小是遠小于50000。所以利用線程池的服務器程序不會為了創建50000而在處理請求時浪費時間,從而提高效率。
??? 代碼實現中并沒有實現任務接口,而是把Runnable對象加入到線程池管理器(ThreadPool),然后剩下的事情就由線程池管理器(ThreadPool)來完成了
?
package mine.util.thread; import java.util.LinkedList; import java.util.List; /** * 線程池類,線程管理器:創建線程,執行任務,銷毀線程,獲取線程基本信息 */ public final class ThreadPool { // 線程池中默認線程的個數為5 private static int worker_num = 5; // 工作線程 private WorkThread[] workThrads; // 未處理的任務 private static volatile int finished_task = 0; // 任務隊列,作為一個緩沖,List線程不安全 private List<Runnable> taskQueue = new LinkedList<Runnable>(); private static ThreadPool threadPool; // 創建具有默認線程個數的線程池 private ThreadPool() { this(5); } // 創建線程池,worker_num為線程池中工作線程的個數 private ThreadPool(int worker_num) { ThreadPool.worker_num = worker_num; workThrads = new WorkThread[worker_num]; for (int i = 0; i < worker_num; i++) { workThrads[i] = new WorkThread(); workThrads[i].start();// 開啟線程池中的線程 } } // 單態模式,獲得一個默認線程個數的線程池 public static ThreadPool getThreadPool() { return getThreadPool(ThreadPool.worker_num); } // 單態模式,獲得一個指定線程個數的線程池,worker_num(>0)為線程池中工作線程的個數 // worker_num<=0創建默認的工作線程個數 public static ThreadPool getThreadPool(int worker_num1) { if (worker_num1 <= 0) worker_num1 = ThreadPool.worker_num; if (threadPool == null) threadPool = new ThreadPool(worker_num1); return threadPool; } // 執行任務,其實只是把任務加入任務隊列,什么時候執行有線程池管理器覺定 public void execute(Runnable task) { synchronized (taskQueue) { taskQueue.add(task); taskQueue.notify(); } } // 批量執行任務,其實只是把任務加入任務隊列,什么時候執行有線程池管理器覺定 public void execute(Runnable[] task) { synchronized (taskQueue) { for (Runnable t : task) taskQueue.add(t); taskQueue.notify(); } } // 批量執行任務,其實只是把任務加入任務隊列,什么時候執行有線程池管理器覺定 public void execute(List<Runnable> task) { synchronized (taskQueue) { for (Runnable t : task) taskQueue.add(t); taskQueue.notify(); } } // 銷毀線程池,該方法保證在所有任務都完成的情況下才銷毀所有線程,否則等待任務完成才銷毀 public void destroy() { while (!taskQueue.isEmpty()) {// 如果還有任務沒執行完成,就先睡會吧 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } // 工作線程停止工作,且置為null for (int i = 0; i < worker_num; i++) { workThrads[i].stopWorker(); workThrads[i] = null; } threadPool=null; taskQueue.clear();// 清空任務隊列 } // 返回工作線程的個數 public int getWorkThreadNumber() { return worker_num; } // 返回已完成任務的個數,這里的已完成是只出了任務隊列的任務個數,可能該任務并沒有實際執行完成 public int getFinishedTasknumber() { return finished_task; } // 返回任務隊列的長度,即還沒處理的任務個數 public int getWaitTasknumber() { return taskQueue.size(); } // 覆蓋toString方法,返回線程池信息:工作線程個數和已完成任務個數 @Override public String toString() { return "WorkThread number:" + worker_num + " finished task number:" + finished_task + " wait task number:" + getWaitTasknumber(); } /** * 內部類,工作線程 */ private class WorkThread extends Thread { // 該工作線程是否有效,用于結束該工作線程 private boolean isRunning = true; /* * 關鍵所在啊,如果任務隊列不空,則取出任務執行,若任務隊列空,則等待 */ @Override public void run() { Runnable r = null; while (isRunning) {// 注意,若線程無效則自然結束run方法,該線程就沒用了 synchronized (taskQueue) { while (isRunning && taskQueue.isEmpty()) {// 隊列為空 try { taskQueue.wait(20); } catch (InterruptedException e) { e.printStackTrace(); } } if (!taskQueue.isEmpty()) r = taskQueue.remove(0);// 取出任務 } if (r != null) { r.run();// 執行任務 } finished_task++; r = null; } } // 停止工作,讓該線程自然執行完run方法,自然結束 public void stopWorker() { isRunning = false; } } }注意!多線程安全,或者說synchronize 一般是用在“增”,“刪”,“改”代碼段上;
測試代碼:
package mine.util.thread; //測試線程池 public class TestThreadPool { public static void main(String[] args) { // 創建3個線程的線程池 ThreadPool t = ThreadPool.getThreadPool(3); t.execute(new Runnable[] { new Task(), new Task(), new Task() }); t.execute(new Runnable[] { new Task(), new Task(), new Task() }); System.out.println(t); t.destroy();// 所有線程都執行完成才destory System.out.println(t); } // 任務類 static class Task implements Runnable { private static volatile int i = 1; @Override public void run() {// 執行任務 System.out.println("任務 " + (i++) + " 完成"); } } }運行結果:
WorkThread number:3? finished task number:0? wait task number:6
任務 1 完成
任務 2 完成
任務 3 完成
任務 4 完成
任務 5 完成
任務 6 完成
WorkThread number:3? finished task number:6? wait task number:0
分析:由于并沒有任務接口,傳入的可以是自定義的任何任務,所以線程池并不能準確的判斷該任務是否真正的已經完成(真正完成該任務是這個任務的run方法執行完畢),只能知道該任務已經出了任務隊列,正在執行或者已經完成。
?
2、Java類庫中提供的線程池簡介:
???? java提供的線程池更加強大,相信理解線程池的工作原理,看類庫中的線程池就不會感到陌生了。
在Java5之后,并 發線程這塊發生了根本的變化,最重要的莫過于新的啟動、調度、管理線程的一大堆API了。在Java5以后,通過Executor來啟動線程比用 Thread的start()更好。在新特征中,可以很容易控制線程的啟動、執行和關閉過程,還可以很容易使用線程池的特性。
參考:https://www.cnblogs.com/mokafamily/p/3558886.html
參考:https://www.cnblogs.com/zhujiabin/p/5404771.html
下面我們來詳細了解下這些線程池。
2、Java 線程池
Java通過Executors提供四種線程池,分別為:
newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
newFixedThreadPool 創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。
newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行。
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
當你挑選完的線程池后就需要創建以及使用線程池:
大概步驟為以下3步:
?(1)調用執行器類(Executors)的靜態方法來創建線程池
(2)調用線程池的submit方法提交Runnable或Callable對象
(3)當不需要添加更多的任務時,調用shutdown關閉入口
?
下面通過代碼來逐步操作:
//創建線程池對象ExecutorService service = Executors.newCachedThreadPool();//創建一個用于遞增輸出i值的runnable對象Runnable runnable = new Runnable() {@Overridepublic void run() {for (int i = 0; i < 400; i++) {System.out.println(i);}}};//調用線程池的submit方法傳入runnable(傳入的runnable將會自動執行)service.submit(runnable);service.submit(runnable);//當不需要傳入更多的任務時調用shutdown方法來關閉入口service.shutdown();需要注意的是如果希望直接停止線程池的一切任務是無法通過shutdown來操作的,因為shutdown僅僅是關閉了入口,但是已經加入的任務還是會繼續執行的,這時我們可以調用線程池的shutdownNow方法來操作,shutdownNow的作用是用來關閉線程池的入口并且會嘗試終止所有當前線程池內的任務。
//用來關閉線程池入口以及終止所有正在執行的任務service.shutdownNow();service的submit方法會返回一個Future<?>類型的對象,然而這是一個怎樣的類型呢?讓我們來看一下api中的方法摘要:
?
?
?
? 從方法摘要中可以看出該對象用于在加入線程池以后能夠對此任務進行取消,查看狀態等操作,如果說在加入線程池以后有可能會取消此任務的話就需要,在submit的時候就需要保存好Future對象。
?
//保存Future<?>Future<?> run2 = service.submit(runnable);//用于查看是否已經執行完畢,返回類型為booleanSystem.out.println(run2.isDone());//取消任務,如果需要中斷的話參數為truerun2.cancel(true);3.線程池實例:
(1). newCachedThreadPool
創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。示例代碼如下:
線程池為無限大,當執行第二個任務時第一個任務已經完成,會復用執行第一個任務的線程,而不用每次新建線程。
注意! executeService.submit() 有返回值 返回類型為future ,exeuteService.execute() 沒有返回值
?(2). newFixedThreadPool
創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。示例代碼如下:
?
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int index = i; fixedThreadPool.execute(new Runnable() {@Override public void run() { try { System.out.println(index); Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); }因為線程池大小為3,每個任務輸出index后sleep 2秒,所以每兩秒打印3個數字。
定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()。可參考PreloadDataCache。
?(3) newScheduledThreadPool
創建一個定長線程池,支持定時及周期性任務執行。延遲執行示例代碼如下:
表示延遲3秒執行。
定期執行示例代碼如下:
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {@Override public void run() { System.out.println("delay 1 seconds, and excute every 3 seconds"); } }, 1, 3, TimeUnit.SECONDS);表示延遲1秒后每3秒執行一次。
ScheduledExecutorService比Timer更安全,功能更強大,后面會有一篇單獨進行對比。
(4)、newSingleThreadExecutor
創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。示例代碼如下:
?
線程池的作用:
線程池作用就是限制系統中執行線程的數量。
???? 根 據系統的環境情況,可以自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用線程池控制線程數量,其他線程排 隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處于等待。當一個新任務需要運行時,如果線程池 中有等待的工作線程,就可以開始運行了;否則進入等待隊列。
為什么要用線程池:
1.減少了創建和銷毀線程的次數,每個工作線程都可以被重復利用,可執行多個任務。
2.可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最后死機)。
Java里面線程池的頂級接口是Executor,但是嚴格意義上講Executor并不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。
比較重要的幾個類:
| ExecutorService | 真正的線程池接口。 |
| ScheduledExecutorService | 能和Timer/TimerTask類似,解決那些需要任務重復執行的問題。 |
| ThreadPoolExecutor | ExecutorService的默認實現。 |
| ScheduledThreadPoolExecutor | 繼承ThreadPoolExecutor的ScheduledExecutorService接口實現,周期性任務調度的類實現。 |
要配置一個線程池是比較復雜的,尤其是對于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在Executors類里面提供了一些靜態工廠,生成一些常用的線程池。
1. newSingleThreadExecutor
創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當于單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
2.newFixedThreadPool
創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那么線程池會補充一個新線程。
3. newCachedThreadPool
創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,
那么就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(或者說JVM)能夠創建的最大線程大小。
4.newScheduledThreadPool
創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求。
------------------------------------------------------------------------------------------------------
一、創建任務
任務就是一個實現了Runnable接口的類。
創建的時候實run方法即可。
二、執行任務
通過java.util.concurrent.ExecutorService接口對象來執行任務,該接口對象通過工具類java.util.concurrent.Executors的靜態方法來創建。
Executors此包中所定義的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 類的工廠和實用方法。
ExecutorService提供了管理終止的方法,以及可為跟蹤一個或多個異步任務執行狀況而生成 Future 的方法。 可以關閉 ExecutorService,這將導致其停止接受新任務。關閉后,執行程序將最后終止,這時沒有任務在執行,也沒有任務在等待執行,并且無法提交新任 務。
executorService.execute(new TestRunnable());
1、創建ExecutorService
通過工具類java.util.concurrent.Executors的靜態方法來創建。
Executors此包中所定義的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 類的工廠和實用方法。
比如,創建一個ExecutorService的實例,ExecutorService實際上是一個線程池的管理工具:
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorService executorService = Executors.newFixedThreadPool(3);
ExecutorService executorService = Executors.newSingleThreadExecutor();
2、將任務添加到線程去執行
當將一個任務添加到線程池中的時候,線程池會為每個任務創建一個線程,該線程會在之后的某個時刻自動執行。
三、關閉執行服務對象
executorService.shutdown();
?
四、綜合實例
package concurrent;import java.util.concurrent.ExecutorService;? import java.util.concurrent.Executors;public class TestCachedThreadPool {?public static void main(String[] args) {? //????????????????ExecutorService executorService = Executors.newCachedThreadPool();?ExecutorService executorService = Executors.newFixedThreadPool(5); //?????????ExecutorService executorService = Executors.newSingleThreadExecutor();for (int i = 0; i < 5; i++) {?executorService.execute(new TestRunnable());?System.out.println("************* a" + i + " *************");?}?executorService.shutdown();?}? }class TestRunnable implements Runnable {?public void run() {?System.out.println(Thread.currentThread().getName() + "線程被調用了。");?while (true) {?try {?Thread.sleep(5000);?System.out.println(Thread.currentThread().getName());?} catch (InterruptedException e) {?e.printStackTrace();?}?}?}? }運行結果:************* a0 *************? ************* a1 *************? pool-1-thread-2線程被調用了。? ************* a2 *************? pool-1-thread-3線程被調用了。? pool-1-thread-1線程被調用了。? ************* a3 *************? ************* a4 *************? pool-1-thread-4線程被調用了。? pool-1-thread-5線程被調用了。? pool-1-thread-2? pool-1-thread-1? pool-1-thread-3? pool-1-thread-5? pool-1-thread-4? pool-1-thread-2? pool-1-thread-1? pool-1-thread-3? pool-1-thread-5? pool-1-thread-4??
五、獲取任務的執行的返回值
在Java5之 后,任務分兩類:一類是實現了Runnable接口的類,一類是實現了Callable接口的類。兩者都可以被ExecutorService執行,但是 Runnable任務沒有返回值,而Callable任務有返回值。并且Callable的call()方法只能通過ExecutorService的 submit(Callable<T> task) 方法來執行,并且返回一個 <T> Future<T>,是表示任務等待完成的 Future.
public interface Callable<V>返回結果并且可能拋出異常的任務。實現者定義了一個不帶任何參數的叫做 call 的方法。
Callable 接口類似于 Runnable,兩者都是為那些其實例可能被另一個線程執行的類設計的。但是 Runnable 不會返回結果,并且無法拋出經過檢查的異常。
Executors 類包含一些從其他普通形式轉換成 Callable 類的實用方法。
Callable中的call()方法類似Runnable的run()方法,就是前者有返回值,后者沒有。
當將一個Callable的對象傳遞給ExecutorService的submit方法,則該call方法自動在一個線程上執行,并且會返回執行結果Future對象。
同樣,將Runnable的對象傳遞給ExecutorService的submit方法,則該run方法自動在一個線程上執行,并且會返回執行結果Future對象,但是在該Future對象上調用get方法,將返回null.
遺憾的是,在Java API文檔中,這塊介紹的很糊涂,估計是翻譯人員還沒搞清楚的緣故吧。或者說是注釋不到位。下面看個例子:
import java.util.ArrayList;? import java.util.List;? import java.util.concurrent.*;public class CallableDemo {?public static void main(String[] args) {?ExecutorService executorService = Executors.newCachedThreadPool();?List<Future<String>> resultList = new ArrayList<Future<String>>();//創建10個任務并執行?for (int i = 0; i < 10; i++) {?//使用ExecutorService執行Callable類型的任務,并將結果保存在future變量中?Future<String> future = executorService.submit(new TaskWithResult(i));?//將任務執行結果存儲到List中?resultList.add(future);?}//遍歷任務的結果?for (Future<String> fs : resultList) {?try {?System.out.println(fs.get());?????//打印各個線程(任務)執行的結果?} catch (InterruptedException e) {?e.printStackTrace();?} catch (ExecutionException e) {?e.printStackTrace();?} finally {?//啟動一次順序關閉,執行以前提交的任務,但不接受新任務。如果已經關閉,則調用沒有其他作用。?executorService.shutdown();?}?}?}? }class TaskWithResult implements Callable<String> {?private int id;public TaskWithResult(int id) {?this.id = id;?}public String call() throws Exception {?System.out.println("call()方法被自動調用,干活!!!?????????????" + Thread.currentThread().getName());?//一個模擬耗時的操作?for (int i = 999999; i > 0; i--) ;?return "call()方法被自動調用,任務的結果是:" + id + "????" + Thread.currentThread().getName();?}? }運行結果:call()方法被自動調用,干活!!!?????????????pool-1-thread-1? call()方法被自動調用,干活!!!?????????????pool-1-thread-3? call()方法被自動調用,干活!!!?????????????pool-1-thread-4? call()方法被自動調用,干活!!!?????????????pool-1-thread-6? call()方法被自動調用,干活!!!?????????????pool-1-thread-2? call()方法被自動調用,干活!!!?????????????pool-1-thread-5? call()方法被自動調用,任務的結果是:0????pool-1-thread-1? call()方法被自動調用,任務的結果是:1????pool-1-thread-2? call()方法被自動調用,干活!!!?????????????pool-1-thread-2? call()方法被自動調用,干活!!!?????????????pool-1-thread-6? call()方法被自動調用,干活!!!?????????????pool-1-thread-4? call()方法被自動調用,任務的結果是:2????pool-1-thread-3? call()方法被自動調用,干活!!!?????????????pool-1-thread-3? call()方法被自動調用,任務的結果是:3????pool-1-thread-4? call()方法被自動調用,任務的結果是:4????pool-1-thread-5? call()方法被自動調用,任務的結果是:5????pool-1-thread-6? call()方法被自動調用,任務的結果是:6????pool-1-thread-2? call()方法被自動調用,任務的結果是:7????pool-1-thread-6? call()方法被自動調用,任務的結果是:8????pool-1-thread-4? call()方法被自動調用,任務的結果是:9????pool-1-thread-3Process finished with exit code 0一個 ExecutorService,它使用可能的幾個池線程之一執行每個提交的任務,通常使用 Executors 工廠方法配置。
?
線程池可以解決兩個不 同問題:由于減少了每個任務調用的開銷,它們通常可以在執行大量異步任務時提供增強的性能,并且還可以提供綁定和管理資源(包括執行集合任務時使用的線 程)的方法。每個 ThreadPoolExecutor 還維護著一些基本的統計數據,如完成的任務數。
?
為了便于跨大量上下文 使用,此類提供了很多可調整的參數和擴展掛鉤。但是,強烈建議程序員使用較為方便的 Executors 工廠方法Executors.newCachedThreadPool()(無界線程池,可以進行自動線程回收)、 Executors.newFixedThreadPool(int)(固定大小線程池)和 Executors.newSingleThreadExecutor()(單個后臺線程),它們均為大多數使用場景預定義了設置。
用法示例 下面給出了一個網絡服務的簡單結構,這里線程池中的線程作為傳入的請求。它使用了預先配置的 Executors.newFixedThreadPool(int) 工廠方法:class NetworkService implements Runnable {private final ServerSocket serverSocket;private final ExecutorService pool;public NetworkService(int port, int poolSize)throws IOException {serverSocket = new ServerSocket(port);pool = Executors.newFixedThreadPool(poolSize);}public void run() { // run the servicetry {for (;;) {pool.execute(new Handler(serverSocket.accept()));}} catch (IOException ex) {pool.shutdown();}}}class Handler implements Runnable {private final Socket socket;Handler(Socket socket) { this.socket = socket; }public void run() {// read and service request on socket}} 下列方法分兩個階段關閉 ExecutorService。第一階段調用 shutdown 拒絕傳入任務,然后調用 shutdownNow(如有必要)取消所有遺留的任務:void shutdownAndAwaitTermination(ExecutorService pool) {pool.shutdown(); // Disable new tasks from being submittedtry {// Wait a while for existing tasks to terminateif (!pool.awaitTermination(60, TimeUnit.SECONDS)) {pool.shutdownNow(); // Cancel currently executing tasks// Wait a while for tasks to respond to being cancelledif (!pool.awaitTermination(60, TimeUnit.SECONDS))System.err.println("Pool did not terminate");}} catch (InterruptedException ie) {// (Re-)Cancel if current thread also interruptedpool.shutdownNow();// Preserve interrupt statusThread.currentThread().interrupt();}} 內存一致性效果:線程中向 ExecutorService 提交 Runnable 或 Callable 任務之前的操作 happen-before 由該任務所提取的所有操作,后者依次 happen-before 通過 Future.get() 獲取的結果。轉發:https://blog.csdn.net/w2393040183/article/details/52177572
總結
以上是生活随笔為你收集整理的java中线程池的几种实现方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么torch.nn.Linear的表
- 下一篇: 哪些Amazon erp是可以免费使用的