菜雞一隻~
其實我還是知道我自己的水平的,菜是原罪,還是要不斷的學習成長提高啊!
因此我會看看一些亂七八糟的直播啊,博客啊之類的(雖然往往整整2個小時的直播裏,可能就講了15分鐘的重點,不過有時候會提到某項技術,大概的實現方式和適用的場景,會讓我眼前一亮啊),當發現有一兩個點我之前沒聽說過或者我覺得有意思,我就會記錄下來,等有時間的時候整理成自己的知識!
本文要說的是在java線程池的一些使用和概念(高併發場景下的合併請求操作!)
當然這樣的文章百度還是可以搜索到很多的,我只是自己再總結歸納下,整理成自己的知識~
概念:
大家應該都知道數據庫連接池這樣的概念,在一個集合裏面創建多個數據庫連接(當然大家做web項目的時候基本都是用各種各樣連接池工具,比如Druid,基本不自己寫),線程池也是這樣的類似的概念,在池子裏面維護多個線程,當有任務的時候,傳入到線程池中執行,那麼就會有這麼幾個問題了!
問題:
1、如何創建線程池才合理?
2、線程池執行任務的方式?
3、執行的任務是否有返回值,如何接收?
回答:
1、java提供的創建線程池的類:ThreadPoolExecutor
該類的開頭有大量的註釋,對於線程池的解釋,大家有興趣可以好好研究下!
//ThreadPoolExecutor最全的構造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
-1.參數說明
線程池核心線程數量:corePoolSize,
線程池最大線程數量: maximumPoolSize,
核心線程空閒存活時長:keepAliveTime,
存活時長的時間單位:TimeUnit unit,
當任務數量大於核心線程數量之後,先把數據放入隊列:BlockingQueue<Runnable> workQueue,
如何創建線程(是否自定義):ThreadFactory threadFactory,
當線程池已經達到最大線程數量,並且隊列也滿了之後,如何處理新來的任務:RejectedExecutionHandler handler
-2.創建方式
大家會看到有兩種方式,
一種是Executors有一些靜態的方法可以直接調用,例如:
// 定時任務線程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
-3.java提供的線程池有哪些?
newSingleThreadExecutor
創建一個單線程的線程池。 任何時候這個線程池都只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因爲異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
newFixedThreadPool
這個是比較常用的,創建固定大小的線程池,大家需要按照各自項目提交線程任務的多少和每個任務佔用時長來設置該線程池的大小。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小(該線程池核心線程數和最大線程數相等)。線程池的大小一旦達到最大值就會保持不變(因爲keepAliveTime爲0),如果某個線程因爲執行異常而結束,那麼線程池會補充一個新線程。
newCachedThreadPool
創建核心線程數爲0,並且一個可複用之前創建但是現在空閒的線程的線程池,但是因爲最大線程數是:Integer.MAX_VALUE
因此,如果任務過多,並且佔用時間長,就會導致堆外內存溢出:https://blog.csdn.net/oufua/article/details/79237458
newScheduledThreadPool
創建一個線程池支持定時以及週期性執行任務的需求,通過scheduleAtFixedRate或者scheduleWithFixedDelay來設定如何調度,可以用來積累一定的請求或者數據,然後批量執行
newWorkStealingPool
jdk1.8之後多出的線程池,該線程池可以自動調用系統可用的線程數(優點:實際線程數會動態地生長和收縮),並行的執行任務,但是保證不了任務執行有序!有些博客認爲,該線程池適合用在耗時相對較長的任務(我覺得說的有道理,但是沒有實際用過,因此。。。我就不瞎說了)
-4.注意
使用任何的線程池,基本上都是ThreadPoolExecutor的不同參數的實現,因此理解他的參數至關重要,這也是爲什麼網上有些文章說,不要隨便使用Executors來構建線程池的原因,因爲如果設置不當,當任務過多的時候,可能會導致oom而使得java應用掛掉!
2、線程的執行方式
我們需要關注:
並行度(線程池的線程個數corePoolSize、 maximumPoolSize,),任務是否立刻執行(是否使用newScheduledThreadPool),提交的任務數量多於線程池最大數量如何處理新來的任務(RejectedExecutionHandler)
3、是否有返回值
我先總體說一下,如果執行線程如果不需要返回值,那隻需要創建一個Runnable,然後實現run方法,讓線程調用就ok了
如果需要返回值,就需要好好看看Callable和Future
-1.無返回值
如果運行的任務沒有返回值,那就比較簡單了,可以直接執行任務把數據跑到數據庫中等待查詢,或者執行任務結束的時候往消息隊列中發送一條消息,告訴消費者某條任務執行完畢,可以通知相應的用戶
-2.有返回值
如果有返回值,那就需要在發送任務到線程池的線程等待返回結果,因此就需要用到另一套東西了
Callable和Future兄弟,前者是用來設置如何產生結果,後者用來獲取結果
看下面幾個例子大家應該就會明白(具體看代碼中的註釋):
//創建一個Callable,指定call方法中如何執行,如何返回結果
Callable<String> callable = new Callable<String>() {
public String call() throws Exception {
System.out.println("This is ThreadPoolExetor#submit(Callable<T> task) method.");
return "result";
}
};
//如何獲得返回結果,跟如何創建線程有關係
//如果是通過 new Thread().start()或者線程池的execute()這種沒有返回值的方法,我們就需要一個Future對象,來調用get()獲取結果
FutureTask<String> task = new FutureTask<String>(callable);
new Thread(task).start();
//獲取結果
String result = task.get();
//如果是通過線程池的submit方法,會返回Future<T> future,就可以通過future.get()來獲得返回值!
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(callable);
System.out.println(future.get());
//submit中有submit(Runnable task, T result),可以傳入一個result,如果調用正常,並且執行完畢就會返回result出來(有點像返回默認值的機制),比如:
class Task implements Runnable {
@Override
public void run() {
System.out.println("This is ThreadPoolExetor#submit(Runnable task, T result) method.");
}
}
//打印結果:This is ThreadPoolExetor#submit(Runnable task, T result) method.
//打印結果:success
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new Task(), "success");
System.out.println(future.get());
好,希望本文能給不少對於線程池有疑惑的同學帶來一些啓發,線程池在工作中還是比較常用到的,特別對於一些數據庫的長任務,或者高併發的場景!
當然在撰寫該文的時候,我也找到了幾篇不錯的文章,在這裏分享出來~
ThreadPoolExecutor中的submit()方法詳細講解:https://blog.csdn.net/qq_33689414/article/details/72955253(作者:張行之)
線程池詳解:https://www.cnblogs.com/CarpenterLee/p/9558026.html(作者:CarpenterLee)
徹底理解Java的Future模式:https://www.cnblogs.com/cz123/p/7693064.html(作者:大誠摯)
異步編程CompletableFuture實現高併發系統優化之請求合併:https://www.cnblogs.com/itbac/p/11298626.html(作者:北溪)
好了本文就到這裏,其實有點長了,有什麼問題歡迎大家留言!!!