❶ java線程池
java常用的線程池有三種:
1.
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads)創建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程。在任意點,在大多數 nThreads 線程會處於處理任務的活動狀態。如果在所有線程處於活動狀態時提交附加任務,則在有可用線程之前,附加任務將在隊列中等待。如果在關閉前的執行期間由於失敗而導致任何線程終止,那麼一個新線程將代替它執行後續的任務(如果需要)。在某個線程被顯式地關閉之前,池中的線程將一直存在。
參數:
nThreads - 池中的線程數
返回:
新創建的線程池
拋出:
IllegalArgumentException - 如果 nThreads <= 0
2.
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor()創建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。(注意,如果因為在關閉前的執行期間出現失敗而終止了此單個線程,那麼如果需要,一個新線程將代替它執行後續的任務)。可保證順序地執行各個任務,並且在任意給定的時間不會有多個線程是活動的。與其他等效的 newFixedThreadPool(1) 不同,可保證無需重新配置此方法所返回的執行程序即可使用其他的線程。
返回:
新創建的單線程 Executor
3.
newCachedThreadPool
public static ExecutorService newCachedThreadPool()創建一個可根據需要創建新線程的線程池,但是在以前構造的線程可用時將重用它們。對於執行很多短期非同步任務的程序而言,這些線程池通常可提高程序性能。調用 execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鍾未被使用的線程。因此,長時間保持空閑的線程池不會使用任何資源。注意,可以使用 ThreadPoolExecutor 構造方法創建具有類似屬性但細節不同(例如超時參數)的線程池。
返回:
新創建的線程池
❷ java 線程中的run()方法無返回值並且不停止,如何得到線程數據
可以讓線程來實現Callable介面,實現call方法源,可以把你需要的東西return回去,然後如果是線程池的話可以用Future對象的get()方法來接收這個返回值。但是這樣做線程池也只會同步執行一個線程。
❸ 線程池中空閑的線程處於什麼狀態
一:阻塞狀態,線程並沒有銷毀,也沒有得到CPU時間片執行;源碼追蹤:for (;;) {... workQueue.take();...}public E take()...{...while (count.get() == 0) { / /這里就是任務隊列中的消息數量notEmpty.await();}...}public final void await()...{...LockSupport.park(this);...}繼續往下:public static void park(Object blocker) {Thread t = Thread.currentThread();setBlocker(t, blocker);U.park(false, 0L);setBlocker(t, null);}private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();//線程調用該方法,線程將一直阻塞直到超時,或者是中斷條件出現。 public native void park(boolean isAbsolute, long time);上面就是java11線程池中阻塞的源碼追蹤;二.對比object的wait()方法:@FastNativepublic final native void wait(long timeout, int nanos) throws InterruptedException;還有Thread的sleep() 方法:@FastNativeprivate static native void sleep(Object lock, long millis, int nanos)throws...;可見,線程池中使用的阻塞方式並不是Object中的wait(),也不是Thread.sleep() ;這3個方法最終實現都是通過c&c++實現的native方法.三.在<<Java虛擬機(第二版)>>中,對線程狀態有以下介紹:12.4.3狀態轉換Java語言定義了5種線程狀態,在任意一個時間點,一個線程只能有且只有其中的一種狀態,這5種狀態分別如下。1)新建(New):創建後尚未啟動的線程處於這種狀態。2)運行(Runable):Runable包括了操作系統線程狀態中的Running和Ready,也就是處於此狀態的線程有可能正在執行,也有可能正在等待著CPU為它分配執行時間。3)無限期等待(Waiting):處於這種狀態的線程不會被分配CPU執行時間,它們要等待被其他線程顯式地喚醒。以下方法會讓線程陷入無限期的等待狀態:●沒有設置Timeout參數的Object.wait()方法。●沒有設置Timeout參數的Thread.join()方法。●LockSupport.park()方法。4)限期等待(Timed Waiting):處於這種狀態的線程也不會被分配CPU執行時間,不過無須等待被其他線程顯式地喚醒,在一定時間之後它們會由系統自動喚醒。以下方法會讓線程進入限期等待狀態:●Thread.sleep()方法。●設置了Timeout參數的Object.wait()方法。●設置了Timeout參數的Thread.join()方法。●LockSupport.parkNanos()方法。●LockSupport.parkUntil()方法。5)阻塞(Blocked):線程被阻塞了,「阻塞狀態」與「等待狀態」的區別是:「阻塞狀態」在等待著獲取到一個排他鎖,這個事件將在另外一個線程放棄這個鎖的時候發生;而「等待狀態」則是在等待一段時間,或者喚醒動作的發生。在程序等待進入同步區域的時候,線程將進入這種狀態。結束(Terminated):已終止線程的線程狀態,線程已經結束執行。❹ java,線程問題,怎麼判斷線程是否結束啊
通過Thread類中的isAlive()方法判斷線程是否處於活動狀態;
線程啟動後,只要沒有運行完畢,都會返專回true;
❺ java線程池怎麼實現的
多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。
假設一個伺服器完成一項任務所需時間為: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)來完成了
packagemine.util.thread;
importjava.util.LinkedList;
importjava.util.List;
/**
*線程池類,線程管理器:創建線程,執行任務,銷毀線程,獲取線程基本信息
*/
publicfinalclassThreadPool{
//線程池中默認線程的個數為5
privatestaticintworker_num=5;
//工作線程
privateWorkThread[]workThrads;
//未處理的任務
_task=0;
//任務隊列,作為一個緩沖,List線程不安全
privateList<Runnable>taskQueue=newLinkedList<Runnable>();
;
//創建具有默認線程個數的線程池
privateThreadPool(){
this(5);
}
//創建線程池,worker_num為線程池中工作線程的個數
privateThreadPool(intworker_num){
ThreadPool.worker_num=worker_num;
workThrads=newWorkThread[worker_num];
for(inti=0;i<worker_num;i++){
workThrads[i]=newWorkThread();
workThrads[i].start();//開啟線程池中的線程
}
}
//單態模式,獲得一個默認線程個數的線程池
(){
returngetThreadPool(ThreadPool.worker_num);
}
//單態模式,獲得一個指定線程個數的線程池,worker_num(>0)為線程池中工作線程的個數
//worker_num<=0創建默認的工作線程個數
(intworker_num1){
if(worker_num1<=0)
worker_num1=ThreadPool.worker_num;
if(threadPool==null)
threadPool=newThreadPool(worker_num1);
returnthreadPool;
}
//執行任務,其實只是把任務加入任務隊列,什麼時候執行有線程池管理器覺定
publicvoidexecute(Runnabletask){
synchronized(taskQueue){
taskQueue.add(task);
taskQueue.notify();
}
}
//批量執行任務,其實只是把任務加入任務隊列,什麼時候執行有線程池管理器覺定
publicvoidexecute(Runnable[]task){
synchronized(taskQueue){
for(Runnablet:task)
taskQueue.add(t);
taskQueue.notify();
}
}
//批量執行任務,其實只是把任務加入任務隊列,什麼時候執行有線程池管理器覺定
publicvoidexecute(List<Runnable>task){
synchronized(taskQueue){
for(Runnablet:task)
taskQueue.add(t);
taskQueue.notify();
}
}
//銷毀線程池,該方法保證在所有任務都完成的情況下才銷毀所有線程,否則等待任務完成才銷毀
publicvoiddestroy(){
while(!taskQueue.isEmpty()){//如果還有任務沒執行完成,就先睡會吧
try{
Thread.sleep(10);
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
//工作線程停止工作,且置為null
for(inti=0;i<worker_num;i++){
workThrads[i].stopWorker();
workThrads[i]=null;
}
threadPool=null;
taskQueue.clear();//清空任務隊列
}
//返回工作線程的個數
publicintgetWorkThreadNumber(){
returnworker_num;
}
//返回已完成任務的個數,這里的已完成是只出了任務隊列的任務個數,可能該任務並沒有實際執行完成
(){
returnfinished_task;
}
//返回任務隊列的長度,即還沒處理的任務個數
publicintgetWaitTasknumber(){
returntaskQueue.size();
}
//覆蓋toString方法,返回線程池信息:工作線程個數和已完成任務個數
@Override
publicStringtoString(){
return"WorkThreadnumber:"+worker_num+"finishedtasknumber:"
+finished_task+"waittasknumber:"+getWaitTasknumber();
}
/**
*內部類,工作線程
*/
{
//該工作線程是否有效,用於結束該工作線程
privatebooleanisRunning=true;
/*
*關鍵所在啊,如果任務隊列不空,則取出任務執行,若任務隊列空,則等待
*/
@Override
publicvoidrun(){
Runnabler=null;
while(isRunning){//注意,若線程無效則自然結束run方法,該線程就沒用了
synchronized(taskQueue){
while(isRunning&&taskQueue.isEmpty()){//隊列為空
try{
taskQueue.wait(20);
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
if(!taskQueue.isEmpty())
r=taskQueue.remove(0);//取出任務
}
if(r!=null){
r.run();//執行任務
}
finished_task++;
r=null;
}
}
//停止工作,讓該線程自然執行完run方法,自然結束
publicvoidstopWorker(){
isRunning=false;
}
}
}
❻ 什麼是java線程池
找的資料,你看一下吧:
多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。
假設一個伺服器完成一項任務所需時間為: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而在處理請求時浪費時間,從而提高效率。
❼ java 線程池 工作隊列是如何工作的
使用線程池的好處
1、降低資源消耗
可以重復利用已創建的線程降低線程創建和銷毀造成的消耗。
2、提高響應速度
當任務到達時,任務可以不需要等到線程創建就能立即執行。
3、提高線程的可管理性
線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控
線程池的工作原理
首先我們看下當一個新的任務提交到線程池之後,線程池是如何處理的
1、線程池判斷核心線程池裡的線程是否都在執行任務。如果不是,則創建一個新的工作線程來執行任務。如果核心線程池裡的線程都在執行任務,則執行第二步。
2、線程池判斷工作隊列是否已經滿。如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊列里進行等待。如果工作隊列滿了,則執行第三步
3、線程池判斷線程池的線程是否都處於工作狀態。如果沒有,則創建一個新的工作線程來執行任務。如果已經滿了,則交給飽和策略來處理這個任務
線程池飽和策略
這里提到了線程池的飽和策略,那我們就簡單介紹下有哪些飽和策略:
AbortPolicy
為Java線程池默認的阻塞策略,不執行此任務,而且直接拋出一個運行時異常,切記ThreadPoolExecutor.execute需要try catch,否則程序會直接退出。
DiscardPolicy
直接拋棄,任務不執行,空方法
DiscardOldestPolicy
從隊列裡面拋棄head的一個任務,並再次execute 此task。
CallerRunsPolicy
在調用execute的線程裡面執行此command,會阻塞入口
用戶自定義拒絕策略(最常用)
實現RejectedExecutionHandler,並自己定義策略模式
下我們以ThreadPoolExecutor為例展示下線程池的工作流程圖
3.jpg
關鍵方法源碼分析
我們看看核心方法添加到線程池方法execute的源碼如下:
// //Executes the given task sometime in the future. The task //may execute in a new thread or in an existing pooled thread. // // If the task cannot be submitted for execution, either because this // executor has been shutdown or because its capacity has been reached, // the task is handled by the current {@code RejectedExecutionHandler}. // // @param command the task to execute // @throws RejectedExecutionException at discretion of // {@code RejectedExecutionHandler}, if the task // cannot be accepted for execution // @throws NullPointerException if {@code command} is null // public void execute(Runnable command) { if (command == null) throw new NullPointerException(); // // Proceed in 3 steps: // // 1. If fewer than corePoolSize threads are running, try to // start a new thread with the given command as its first // task. The call to addWorker atomically checks runState and // workerCount, and so prevents false alarms that would add // threads when it shouldn't, by returning false. // 翻譯如下: // 判斷當前的線程數是否小於corePoolSize如果是,使用入參任務通過addWord方法創建一個新的線程, // 如果能完成新線程創建exexute方法結束,成功提交任務 // 2. If a task can be successfully queued, then we still need // to double-check whether we should have added a thread // (because existing ones died since last checking) or that // the pool shut down since entry into this method. So we // recheck state and if necessary roll back the enqueuing if // stopped, or start a new thread if there are none. // 翻譯如下: // 在第一步沒有完成任務提交;狀態為運行並且能否成功加入任務到工作隊列後,再進行一次check,如果狀態 // 在任務加入隊列後變為了非運行(有可能是在執行到這里線程池shutdown了),非運行狀態下當然是需要 // reject;然後再判斷當前線程數是否為0(有可能這個時候線程數變為了0),如是,新增一個線程; // 3. If we cannot queue task, then we try to add a new // thread. If it fails, we know we are shut down or saturated // and so reject the task. // 翻譯如下: // 如果不能加入任務到工作隊列,將嘗試使用任務新增一個線程,如果失敗,則是線程池已經shutdown或者線程池 // 已經達到飽和狀態,所以reject這個他任務 // int c = ctl.get(); // 工作線程數小於核心線程數 if (workerCountOf(c) < corePoolSize) { // 直接啟動新線程,true表示會再次檢查workerCount是否小於corePoolSize if (addWorker(command, true)) return; c = ctl.get(); } // 如果工作線程數大於等於核心線程數 // 線程的的狀態未RUNNING並且隊列notfull if (isRunning(c) && workQueue.offer(command)) { // 再次檢查線程的運行狀態,如果不是RUNNING直接從隊列中移除 int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) // 移除成功,拒絕該非運行的任務 reject(command); else if (workerCountOf(recheck) == 0) // 防止了SHUTDOWN狀態下沒有活動線程了,但是隊列里還有任務沒執行這種特殊情況。 // 添加一個null任務是因為SHUTDOWN狀態下,線程池不再接受新任務 addWorker(null, false); } // 如果隊列滿了或者是非運行的任務都拒絕執行 else if (!addWorker(command, false)) reject(command); }
❽ java線程池怎麼實現
要想理解清楚java線程池實現原理,明白下面幾個問題就可以了:
(1):線程池存在哪些狀態,這些狀態之間是如何進行切換的呢?
(2):線程池的種類有哪些?
(3):創建線程池需要哪些參數,這些參數的具體含義是什麼?
(4):將任務添加到線程池之後運行流程?
(5):線程池是怎麼做到重用線程的呢?
(6):線程池的關閉
首先回答第一個問題:線程池存在哪些狀態;
查看ThreadPoolExecutor源碼便知曉:
[java]view plain
//runStateisstoredinthehigh-orderbits
privatestaticfinalintRUNNING=-1<<COUNT_BITS;
privatestaticfinalintSHUTDOWN=0<<COUNT_BITS;
privatestaticfinalintSTOP=1<<COUNT_BITS;
privatestaticfinalintTIDYING=2<<COUNT_BITS;
=3<<COUNT_BITS;
存在5種狀態:
<1>Running:可以接受新任務,同時也可以處理阻塞隊列裡面的任務;
<2>Shutdown:不可以接受新任務,但是可以處理阻塞隊列裡面的任務;
<3>Stop:不可以接受新任務,也不處理阻塞隊列裡面的任務,同時還中斷正在處理的任務;
<4>Tidying:屬於過渡階段,在這個階段表示所有的任務已經執行結束了,當前線程池中是不存在有效的線程的,並且將要調用terminated方法;
<5>Terminated:終止狀態,這個狀態是在調用完terminated方法之後所處的狀態;
那麼這5種狀態之間是如何進行轉換的呢?查看ThreadPoolExecutor源碼裡面的注釋便可以知道啦:
[java]view plain
*RUNNING->SHUTDOWN
*Oninvocationofshutdown(),perhapsimplicitlyinfinalize()
*(RUNNINGorSHUTDOWN)->STOP
*OninvocationofshutdownNow()
*SHUTDOWN->TIDYING
*Whenbothqueueandpoolareempty
*STOP->TIDYING
*Whenpoolisempty
*TIDYING->TERMINATED
*Whentheterminated()hookmethodhascompleted
從上面可以看到,在調用shutdown方法的時候,線程池狀態會從Running轉換成Shutdown;在調用shutdownNow方法的時候,線程池狀態會從Running/Shutdown轉換成Stop;在阻塞隊列為空同時線程池為空的情況下,線程池狀態會從Shutdown轉換成Tidying;在線程池為空的情況下,線程池狀態會從Stop轉換成Tidying;當調用terminated方法之後,線程池狀態會從Tidying轉換成Terminate;
在明白了線程池的各個狀態以及狀態之間是怎麼進行切換之後,我們來看看第二個問題,線程池的種類:
(1):CachedThreadPool:緩存線程池,該類線程池中線程的數量是不確定的,理論上可以達到Integer.MAX_VALUE個,這種線程池中的線程都是非核心線程,既然是非核心線程,那麼就存在超時淘汰機制了,當裡面的某個線程空閑時間超過了設定的超時時間的話,就會回收掉該線程;
(2):FixedThreadPool:固定線程池,這類線程池中是只存在核心線程的,對於核心線程來說,如果我們不設置allowCoreThreadTimeOut屬性的話是不存在超時淘汰機制的,這類線程池中的corePoolSize的大小是等於maximumPoolSize大小的,也就是說,如果線程池中的線程都處於活動狀態的話,如果有新任務到來,他是不會開辟新的工作線程來處理這些任務的,只能將這些任務放到阻塞隊列裡面進行等到,直到有核心線程空閑為止;
(3):ScheledThreadPool:任務線程池,這種線程池中核心線程的數量是固定的,而對於非核心線程的數量是不限制的,同時對於非核心線程是存在超時淘汰機制的,主要適用於執行定時任務或者周期性任務的場景;
(4):SingleThreadPool:單一線程池,線程池裡面只有一個線程,同時也不存在非核心線程,感覺像是FixedThreadPool的特殊版本,他主要用於確保任務在同一線程中的順序執行,有點類似於進行同步吧;
接下來我們來看第三個問題,創建線程池需要哪些參數:
同樣查看ThreadPoolExecutor源碼,查看創建線程池的構造函數:
[java]view plain
publicThreadPoolExecutor(intcorePoolSize,
intmaximumPoolSize,
longkeepAliveTime,
TimeUnitunit,
BlockingQueue<Runnable>workQueue,
ThreadFactorythreadFactory,
)
不管你調用的是ThreadPoolExecutor的哪個構造函數,最終都會執行到這個構造函數的,這個構造函數有7個參數,正是由於對這7個參數值的賦值不同,造成生成不同類型的線程池,比如我們常見的CachedThreadPoolExecutor、FixedThreadPoolExecutor
SingleThreadPoolExecutor、ScheledThreadPoolExecutor,我們老看看這幾個參數的具體含義:
<1>corePoolSize:線程池中核心線程的數量;當提交一個任務到線程池的時候,線程池會創建一個線程來執行執行任務,即使有其他空閑的線程存在,直到線程數達到corePoolSize時不再創建,這時候會把提交的新任務放入到阻塞隊列中,如果調用了線程池的preStartAllCoreThreads方法,則會在創建線程池的時候初始化出來核心線程;
<2>maximumPoolSize:線程池允許創建的最大線程數;如果阻塞隊列已經滿了,同時已經創建的線程數小於最大線程數的話,那麼會創建新的線程來處理阻塞隊列中的任務;
<3>keepAliveTime:線程活動保持時間,指的是工作線程空閑之後繼續存活的時間,默認情況下,這個參數只有線程數大於corePoolSize的時候才會起作用,即當線程池中的線程數目大於corePoolSize的時候,如果某一個線程的空閑時間達到keepAliveTime,那麼這個線程是會被終止的,直到線程池中的線程數目不大於corePoolSize;如果調用allowCoreThreadTimeOut的話,在線程池中線程數量不大於corePoolSize的時候,keepAliveTime參數也可以起作用的,知道線程數目為0為止;
<4>unit:參數keepAliveTime的時間單位;
<5>workQueue:阻塞隊列;用於存儲等待執行的任務,有四種阻塞隊列類型,ArrayBlockingQueue(基於數組的有界阻塞隊列)、LinkedBlockingQueue(基於鏈表結構的阻塞隊列)、SynchronousQueue(不存儲元素的阻塞隊列)、PriorityBlockingQueue(具有優先順序的阻塞隊列);
<6>threadFactory:用於創建線程的線程工廠;
<7>handler:當阻塞隊列滿了,且沒有空閑線程的情況下,也就是說這個時候,線程池中的線程數目已經達到了最大線程數量,處於飽和狀態,那麼必須採取一種策略來處理新提交的任務,我們可以自己定義處理策略,也可以使用系統已經提供給我們的策略,先來看看系統為我們提供的4種策略,AbortPolicy(直接拋出異常)、CallerRunsPolicy(只有調用者所在的線程來運行任務)、DiscardOldestPolicy(丟棄阻塞隊列中最近的一個任務,並執行當前任務)、Discard(直接丟棄);
接下來就是將任務添加到線程池之後的運行流程了;
我們可以調用submit或者execute方法,兩者最大的區別在於,調用submit方法的話,我們可以傳入一個實現Callable介面的對象,進而能在當前任務執行結束之後通過Future對象獲得任務的返回值,submit內部實際上還是執行的execute方法;而調用execute方法的話,是不能獲得任務執行結束之後的返回值的;此外,調用submit方法的話是可以拋出異常的,但是調用execute方法的話,異常在其內部得到了消化,也就是說異常在其內部得到了處理,不會向外傳遞的;
因為submit方法最終也是會執行execute方法的,因此我們只需要了解execute方法就可以了:
在execute方法內部會分三種情況來進行處理:
<1>:首先判斷當前線程池中的線程數量是否小於corePoolSize,如果小於的話,則直接通過addWorker方法創建一個新的Worker對象來執行我們當前的任務;
<2>:如果說當前線程池中的線程數量大於corePoolSize的話,那麼會嘗試將當前任務添加到阻塞隊列中,然後第二次檢查線程池的狀態,如果線程池不在Running狀態的話,會將剛剛添加到阻塞隊列中的任務移出,同時拒絕當前任務請求;如果第二次檢查發現當前線程池處於Running狀態的話,那麼會查看當前線程池中的工作線程數量是否為0,如果為0的話,就會通過addWorker方法創建一個Worker對象出來處理阻塞隊列中的任務;
<3>:如果原先線程池就不處於Running狀態或者我們剛剛將當前任務添加到阻塞隊列的時候出現錯誤的話,那麼會去嘗試通過addWorker創建新的Worker來處理當前任務,如果添加失敗的話,則拒絕當前任務請求;
可以看到在上面的execute方法中,我們僅僅只是檢查了當前線程池中的線程數量有沒有超過corePoolSize的情況,那麼當前線程池中的線程數量有沒有超過maximumPoolSize是在哪裡檢測的呢?實際上是在addWorker方法裡面了,我們可以看下addWorker裡面的一段代碼:
[java]view plain
if(wc>=CAPACITY||
wc>=(core?corePoolSize:maximumPoolSize))
returnfalse;
如果當前線程數量超過maximumPoolSize的話,直接就會調用return方法,返回false;
其實到這里我們很明顯可以知道,一個線程池中線程的數量實際上就是這個線程池中Worker的數量,如果Worker的大小超過了corePoolSize,那麼任務都在阻塞隊列裡面了,Worker是Java對我們任務的一個封裝類,他的聲明是醬紫的:
[java]view plain
privatefinalclassWorker
implementsRunnable
可以看到他實現了Runnable介面,他是在addWorker方法裡面通過new Worker(firstTask)創建的,我們來看看他的構造函數就知道了:
[java]view plain
Worker(RunnablefirstTask){
setState(-1);//
this.firstTask=firstTask;
this.thread=getThreadFactory().newThread(this);
}
而這里的firstTask其實就是我們調用execute或者submit的時候傳入的那個參數罷了,一般來說這些參數是實現Callable或者Runnable介面的;
在通過addWorker方法創建出來Worker對象之後,這個方法的最後會執行Worker內部thread屬性的start方法,而這個thread屬性實際上就是封裝了Worker的Thread,執行他的start方法實際上執行的是Worker的run方法,因為Worker是實現了Runnable介面的,在run方法裡面就會執行runWorker方法,而runWorker方法裡面首先會判斷當前我們傳入的任務是否為空,不為空的話直接就會執行我們通過execute或者submit方法提交的任務啦,注意一點就是我們雖然會通過submit方法提交實現了Callable介面的對象,但是在調用submit方法的時候,其實是會將Callable對象封裝成實現了Runnable介面對象的,不信我們看看submit方法源碼是怎麼實現的:
[java]view plain
public<T>Future<T>submit(Callable<T>task){
if(task==null)thrownewNullPointerException();
RunnableFuture<T>ftask=newTaskFor(task);
execute(ftask);
returnftask;
}
看到沒有呢,實際上在你傳入實現了Callable介面對象的時候,在submit方法裡面是會將其封裝成RunnableFuture對象的,而RunnableFuture介面是繼承了Runnable介面的;那麼說白了其實就是直接執行我們提交任務的run方法了;如果為空的話,則會通過getTask方法從阻塞隊列裡面拿出一個任務去執行;在任務執行結束之後繼續從阻塞隊列裡面拿任務,直到getTask的返回值為空則退出runWorker內部循環,那麼什麼情況下getTask返回為空呢?查看getTask方法的源碼注釋可以知道:在Worker必須需要退出的情況下getTask會返回空,具體什麼情況下Worker會退出呢?(1):當Worker的數量超過maximumPoolSize的時候;(2):當線程池狀態為Stop的時候;(3):當線程池狀態為Shutdown並且阻塞隊列為空的時候;(4):使用等待超時時間從阻塞隊列中拿數據,但是超時之後仍然沒有拿到數據;
如果runWorker方法退出了它裡面的循環,那麼就說明當前阻塞隊列裡面是沒有任務可以執行的了,你可以看到在runWorker方法內部的finally語句塊中執行了processWorkerExit方法,用來對Worker對象進行回收操作,這個方法會傳入一個參數表示需要刪除的Worker對象;在進行Worker回收的時候會調用tryTerminate方法來嘗試關閉線程池,在tryTerminate方法裡面會檢查是否有Worker在工作,檢查線程池的狀態,沒問題的話就會將當前線程池的狀態過渡到Tidying,之後調用terminated方法,將線程池狀態更新到Terminated;
從上面的分析中,我們可以看出線程池運行的4個階段:
(1):poolSize < corePoolSize,則直接創建新的線程(核心線程)來執行當前提交的任務;
(2):poolSize = corePoolSize,並且此時阻塞隊列沒有滿,那麼會將當前任務添加到阻塞隊列中,如果此時存在工作線程(非核心線程)的話,那麼會由工作線程來處理該阻塞隊列中的任務,如果此時工作線程數量為0的話,那麼會創建一個工作線程(非核心線程)出來;
(3):poolSize = corePoolSize,並且此時阻塞隊列已經滿了,那麼會直接創建新的工作線程(非核心線程)來處理阻塞隊列中的任務;
(4):poolSize = maximumPoolSize,並且此時阻塞隊列也滿了的話,那麼會觸發拒絕機制,具體決絕策略採用的是什麼就要看我們創建ThreadPoolExecutor的時候傳入的RejectExecutionHandler參數了;
接下來就是線程池是怎麼做到重用線程的呢?
個人認為線程池裡面重用線程的工作是在getTask裡面實現的,在getTask裡面是存在兩個for死循環嵌套的,他會不斷的從阻塞對列裡面取出需要執行的任務,返回給我們的runWorker方法裡面,而在runWorker方法裡面只要getTask返回的任務不是空就會執行該任務的run方法來處理它,這樣一直執行下去,直到getTask返回空為止,此時的情況就是阻塞隊列裡面沒有任務了,這樣一個線程處理完一個任務之後接著再處理阻塞隊列中的另一個任務,當然在線程池中的不同線程是可以並發處理阻塞隊列中的任務的,最後在阻塞隊列內部不存在任務的時候會去判斷是否需要回收Worker對象,其實Worker對象的個數就是線程池中線程的個數,至於什麼情況才需要回收,上面已經說了,就是四種情況了;
最後就是線程池是怎樣被關閉的呢?
涉及到線程池的關閉,需要用到兩個方法,shutdown和shutdownNow,他們都是位於ThreadPoolExecutor裡面的,對於shutdown的話,他會將線程池狀態切換成Shutdown,此時是不會影響對阻塞隊列中任務執行的,但是會拒絕執行新加進來的任務,同時會回收閑置的Worker;而shutdownNow方法會將線程池狀態切換成Stop,此時既不會再去處理阻塞隊列裡面的任務,也不會去處理新加進來的任務,同時會回收所有Worker;
❾ Java線程池
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
❿ 超詳細的線程池使用解析
Java 中線程池是運用場景最多的並發框架,幾乎所有需要非同步或並發執行任務的程序都可以使用線程池。合理的使用線程池可以帶來多個好處:
(1) 降低資源消耗 。通過重復利用已創建的線程降低線程在創建和銷毀時造成的消耗。
(2) 提高響應速度 。當處理執行任務時,任務可以不需要等待線程的創建就能立刻執行。
(3) 提高線程的可管理性 。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。
線程池的處理流程如上圖所示
線程池中通過 ctl 欄位來表示線程池中的當前狀態,主池控制狀態 ctl 是 AtomicInteger 類型,包裝了兩個概念欄位:workerCount 和 runState,workerCount 表示有效線程數,runState 表示是否正在運行、正在關閉等狀態。使用 ctl 欄位表示兩個概念,ctl 的前 3 位表示線程池狀態,線程池中限制 workerCount 為(2^29 )-1(約 5 億)個線程,而不是 (2^31)-1(20 億)個線程。workerCount 是允許啟動和不允許停止的工作程序的數量。該值可能與實際的活動線程數暫時不同,例如,當 ThreadFactory 在被詢問時未能創建線程時,以及退出線程在終止前仍在執行記時。用戶可見的池大小報告為工作集的當前大小。 runState 提供主要的生命周期控制,取值如下表所示:
runState 隨著時間的推移而改變,在 awaitTermination() 方法中等待的線程將在狀態達到 TERMINATED 時返回。狀態的轉換為:
RUNNING -> SHUTDOWN 在調用 shutdown() 時,可能隱含在 finalize() 中
(RUNNING 或 SHUTDOWN)-> STOP 在調用 shutdownNow() 時
SHUTDOWN -> TIDYING 當隊列和線程池都為空時
STOP -> TIDYING 當線程池為空時
TIDYING -> TERMINATED 當 terminate() 方法完成時
開發人員如果需要在線程池變為 TIDYING 狀態時進行相應的處理,可以通過重載 terminated() 函數來實現。
結合上圖說明線程池 ThreadPoolExecutor 執行流程,使用 execute() 方法提交任務到線程池中執行時分為4種場景:
(1)線程池中運行的線程數量小於 corePoolSize,創建新線程來執行任務。
(2)線程池中運行線程數量不小於 corePoolSize,將任務加入到阻塞隊列 BlockingQueue。
(3)如果無法將任務加入到阻塞隊列(隊列已滿),創建新的線程來處理任務(這里需要獲取全局鎖)。
(4)當創建新的線程數量使線程池中當前運行線程數量超過 maximumPoolSize,線程池中拒絕任務,調用 RejectedExecutionHandler.rejectedExecution() 方法處理。
源碼分析:
線程池創建線程時,會將線程封裝成工作線程 Worker,Worker 在執行完任務後,還會循環獲取工作隊列里的任務來執行。
創建線程池之前,首先要知道創建線程池中的核心參數:
corePoolSize (核心線程數大小):當提交任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閑的基本線程能夠執行新任務也會創建線程,直到需要執行的任務數大於核心線程數時就不再創建。
runnableTaskQueue (任務隊列):用於保存等待執行任務的阻塞隊列。一般選擇以下幾種:
ArrayBlockingQueue:基於數組的有界阻塞隊列,按照 FIFO 原則對元素進行排序。
LinkedBlockingQueue:基於鏈表的阻塞隊列,按照 FIFO 原則對元素進行排序。
SynchronousQueue:同步阻塞隊列,也是不存儲元素的阻塞隊列。每一個插入操作必須要等到另一個 線程調用移除操作,否則插入操作一直處於阻塞狀態。
PriorityBlockingQueue:優先阻塞隊列,一個具有優先順序的無限阻塞隊列。
maximumPoolSize (最大線程數大小):線程池允許創建的最大線程數,當隊列已滿,並且線程池中的線程數小於最大線程數,則線程池會創建新的線程執行任務。當使用無界隊列時,此參數無用。
RejectedExecutionHandler (拒絕策略):當任務隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須使用拒絕策略來處理新提交的任務。JDK 內置拒絕策略有以下 4 種:
AbortPolicy:直接拋出異常
CallerRunsPolicy:使用調用者所在的線程來執行任務
DiscardOldestPolicy:丟棄隊列中最近的一個任務來執行當前任務
DiscardPolicy:直接丟棄不處理
可以根據應用場景來實現 RejectedExecutionHandler 介面自定義處理策略。
keepAliveTime (線程存活時間):線程池的工作線程空閑後,保持存活的時間。
TimeUnit (存活時間單位):可選單位DAYS(天)、HOURS(小時)、MINUTES(分鍾)、MILLISECONDS(毫秒)、MICROSECONDS(微妙)、NANOSECONDS(納秒)。
ThreadFactory (線程工廠):可以通過線程工廠給創建出來的線程設置有意義的名字。
創建線程池主要分為兩大類,第一種是通過 Executors 工廠類創建線程池,第二種是自定義創建線程池。根據《阿里java開發手冊》中的規范,線程池不允許使用 Executors 去創建,原因是規避資源耗盡的風險。
創建一個單線程化的線程池
創建固定線程數的線程池
以上兩種創建線程池方式使用鏈表阻塞隊列來存放任務,實際場景中可能會堆積大量請求導致 OOM
創建可緩存線程池
允許創建的線程數量最大為 Integer.MAX_VALUE,當創建大量線程時會導致 CPU 處於重負載狀態和 OOM 的發生
向線程池提交任務可以使用兩個方法,分別為 execute() 和 submit()。
execute() 方法用於提交不需要返回值的任務,所以無法判斷任務是否被線程池執行成功。execute() 方法中傳入的是 Runnable 類的實例。
submit() 方法用於提交需要返回值的任務。線程池會返回一個 Future 類型的對象,通過 future 對象可以判斷任務是否執行成功,並且可以通過 future 的 get() 方法來獲取返回值。get() 方法會阻塞當前線程直到任務完成,使用 get(long timeout, TimeUnit unit)方法會阻塞當前線程一段時間後立即返回,這時候可能任務沒有執行完。
可以通過調用線程池的 shutdown() 或shutdownNow() 方法來關閉線程池。他們的原理是遍歷線程池中的工作線程,然後逐個調用 interrupt() 方法來中斷線程,所以無法響應中斷任務可能永遠無法終止。
shutdown() 和 shutdownNow() 方法的區別在於 shutdownNow 方法首先將線程池的狀態設置為 STOP,然後嘗試停止正在執行或暫停任務的線程,並返回等待執行任務的列表,而 shutdown 只是將線程池的狀態設置成 SHUTDOWN 狀態,然後中斷所有沒有正在執行任務的線程。
線程池使用面臨的核心的問題在於: 線程池的參數並不好配置 。一方面線程池的運行機制不是很好理解,配置合理需要強依賴開發人員的個人經驗和知識;另一方面,線程池執行的情況和任務類型相關性較大,IO 密集型和 CPU 密集型的任務運行起來的情況差異非常大,這導致業界並沒有一些成熟的經驗策略幫助開發人員參考。
(1)以任務型為參考的簡單評估:
假設線程池大小的設置(N 為 CPU 的個數)
如果純計算的任務,多線程並不能帶來性能提升,因為 CPU 處理能力是稀缺的資源,相反導致較多的線程切換的花銷,此時建議線程數為 CPU 數量或+1;----為什麼+1?因為可以防止 N 個線程中有一個線程意外中斷或者退出,CPU 不會空閑等待。
如果是 IO 密集型應用, 則線程池大小設置為 2N+1. 線程數 = CPU 核數 目標 CPU 利用率 (1 + 平均等待時間 / 平均工作時間)
(2)以任務數為參考的理想狀態評估:
1)默認值
2)如何設置 * 需要根據相關值來決定 - tasks :每秒的任務數,假設為500~1000 - taskCost:每個任務花費時間,假設為0.1s - responsetime:系統允許容忍的最大響應時間,假設為1s
以上都為理想值,實際情況下要根據機器性能來決定。如果在未達到最大線程數的情況機器 cpu load 已經滿了,則需要通過升級硬體和優化代碼,降低 taskCost 來處理。
(僅為簡單的理想狀態的評估,可作為線程池參數設置的一個參考)
與主業務無直接數據依賴的從業務可以使用非同步線程池來處理,在項目初始化時創建線程池並交給將從業務中的任務提交給非同步線程池執行能夠縮短響應時間。
嚴禁在業務代碼中起線程!!!
當任務需要按照指定順序(FIFO, LIFO, 優先順序)執行時,推薦創建使用單線程化的線程池。
本文章主要說明了線程池的執行原理和創建方式以及推薦線程池參數設置和一般使用場景。在開發中,開發人員需要根據業務來合理的創建和使用線程池達到降低資源消耗,提高響應速度的目的。
原文鏈接:https://juejin.cn/post/7067324722811240479