執行緒池你真不來了解一下嗎?
前言
只有光頭才能變強
回顧前面:
ThreadLocal就是這麼簡單
多執行緒三分鐘就可以入個門了!
多執行緒基礎必要知識點!看了學習多執行緒事半功倍
Java鎖機制瞭解一下
AQS簡簡單單過一遍
Lock鎖子類瞭解一下
本篇主要是講解執行緒池,這是我在多執行緒的倒數第二篇了,
後面還會有一篇死鎖
。主要將多執行緒的基礎
過一遍
,以後
有機會再繼續深入
!
那麼接下來就開始吧,如果文章有錯誤的地方請大家多多包涵,不吝在評論區指正哦~
宣告:本文使用JDK1。8
一、執行緒池簡介
執行緒池可以看做是
執行緒的集合
。在沒有任務時執行緒處於空閒狀態,當請求到來:執行緒池給這個請求分配一個空閒的執行緒,任務完成後回到執行緒池中等待下次任務**(而不是銷燬)
。這樣就
實現了執行緒的
重用
。
我們來看看
如果沒有使用執行緒池
的情況是這樣的:
為每個請求都新開一個執行緒
!
public
class
ThreadPerTaskWebServer
{
public
static
void
main
(
String
[]
args
)
throws
IOException
{
ServerSocket
socket
=
new
ServerSocket
(
80
);
while
(
true
)
{
// 為每個請求都建立一個新的執行緒
final
Socket
connection
=
socket
。
accept
();
Runnable
task
=
()
->
handleRequest
(
connection
);
new
Thread
(
task
)。
start
();
}
}
private
static
void
handleRequest
(
Socket
connection
)
{
// request-handling logic here
}
}
為每個請求都開一個新的執行緒雖然
理論上是可以
的,但是會有
缺點
:
執行緒生命週期的開銷非常高
。每個執行緒都有自己的生命週期,
建立和銷燬執行緒
所花費的時間和資源可能比處理客戶端的任務花費的時間和資源更多,並且還會有某些
空閒執行緒也會佔用資源
。
程式的穩定性和健壯性會下降,每個請求開一個執行緒。如果受到了惡意攻擊或者請求過多(記憶體不足),程式很容易就奔潰掉了。
所以說:我們的
執行緒最好是交由執行緒池來管理
,這樣可以減少對執行緒生命週期的管理,一定程度上提高效能。
二、JDK提供的執行緒池API
JDK給我們提供了
Excutor框架
來使用執行緒池,它是
執行緒池的基礎
。
Executor提供了一種將
“任務提交”與“任務執行”
分離開來的機制(解耦)
下面我們來看看JDK執行緒池的總體api架構:
接下來我們把這些API都過一遍看看:
Executor介面:
ExcutorService介面:
AbstractExecutorService類:
ScheduledExecutorService介面:
ThreadPoolExecutor類:
ScheduledThreadPoolExecutor類:
2。1ForkJoinPool執行緒池
除了ScheduledThreadPoolExecutor和ThreadPoolExecutor類執行緒池以外,還有一個是
JDK1.7新增
的執行緒池:ForkJoinPool執行緒池
於是我們的類圖就可以變得完整一些:
JDK1。7中新增的一個執行緒池,與ThreadPoolExecutor一樣,同樣繼承了AbstractExecutorService。ForkJoinPool是Fork/Join框架的兩大核心類之一。與其它型別的ExecutorService相比,
其主要的不同在於採用了工作竊取演算法(work-stealing)
:所有池中執行緒會嘗試找到並執行已被提交到池中的或由其他執行緒建立的任務。這樣很少有執行緒會處於空閒狀態,非常高效。這使得能夠有效地處理以下情景:
大多數由任務產生大量子任務的情況
;從外部客戶端大量提交小任務到池中的情況。
blog。csdn。net/panweiwei19…
2。2補充:Callable和Future
學到了執行緒池,我們可以很容易地發現:很多的API都有Callable和Future這麼兩個東西。
Future
<?>
submit
(
Runnable
task
)
<
T
>
Future
<
T
>
submit
(
Callable
<
T
>
task
)
其實它們也不是什麼高深的東西~~~
我們可以簡單認為:
Callable就是Runnable的擴充套件
。
Runnable沒有返回值,不能丟擲受檢查的異常,而Callable可以
!
也就是說:當我們的
任務需要返回值
的時,我們就可以使用Callable!
Future一般我們認為是Callable的返回值,但他其實代表的是
任務的生命週期
(當然了,它是能獲取得到Callable的返回值的)
簡單來看一下他們的用法:
public
class
CallableDemo
{
public
static
void
main
(
String
[]
args
)
throws
InterruptedException
,
ExecutionException
{
// 建立執行緒池物件
ExecutorService
pool
=
Executors
。
newFixedThreadPool
(
2
);
// 可以執行Runnable物件或者Callable物件代表的執行緒
Future
<
Integer
>
f1
=
pool
。
submit
(
new
MyCallable
(
100
));
Future
<
Integer
>
f2
=
pool
。
submit
(
new
MyCallable
(
200
));
// V get()
Integer
i1
=
f1
。
get
();
Integer
i2
=
f2
。
get
();
System
。
out
。
println
(
i1
);
System
。
out
。
println
(
i2
);
// 結束
pool
。
shutdown
();
}
}
Callable任務:
public
class
MyCallable
implements
Callable
<
Integer
>
{
private
int
number
;
public
MyCallable
(
int
number
)
{
this
。
number
=
number
;
}
@Override
public
Integer
call
()
throws
Exception
{
int
sum
=
0
;
for
(
int
x
=
1
;
x
<=
number
;
x
++)
{
sum
+=
x
;
}
return
sum
;
}
}
執行完任務之後可以
獲取得到任務返回的資料
:
三、ThreadPoolExecutor詳解
這是用得最多的執行緒池,所以本文會重點講解它。
我們來看看頂部註釋:
3。1內部狀態
變數ctl定義為AtomicInteger,
記錄了“執行緒池中的任務數量”和“執行緒池的狀態”兩個資訊
。
執行緒的狀態:
RUNNING:執行緒池
能夠接受新任務
,以及對新新增的任務進行處理。
SHUTDOWN:執行緒池
不可以接受新任務
,但是可以對已新增的任務進行處理。
STOP:執行緒池
不接收新任務,不處理已新增的任務,並且會中斷正在處理的任務
。
TIDYING:當
所有的任務已終止
,ctl記錄的“任務數量”為0,執行緒池會變為TIDYING狀態。當執行緒池變為TIDYING狀態時,會執行鉤子函式terminated()。terminated()在ThreadPoolExecutor類中是空的,若使用者想線上程池變為TIDYING時,進行相應的處理;可以透過過載terminated()函式來實現。
TERMINATED:執行緒池
徹底終止的狀態
。
各個狀態之間轉換:
3。2已預設實現的池
下面我就列舉三個比較
常見
的實現池:
newFixedThreadPool
newCachedThreadPool
SingleThreadExecutor
如果讀懂了上面對應的策略呀,執行緒數量這些,應該就不會太難看懂了。
3。2。1newFixedThreadPool
一個固定執行緒數的執行緒池,它將返回一個
corePoolSize和maximumPoolSize相等的執行緒池
。
public
static
ExecutorService
newFixedThreadPool
(
int
nThreads
)
{
return
new
ThreadPoolExecutor
(
nThreads
,
nThreads
,
0L
,
TimeUnit
。
MILLISECONDS
,
new
LinkedBlockingQueue
<
Runnable
>());
}
3。2。2newCachedThreadPool
非常有彈性的執行緒池
,對於新的任務,如果此時執行緒池裡沒有空閒執行緒,
執行緒池會毫不猶豫的建立一條新的執行緒去處理這個任務
。
public
static
ExecutorService
newCachedThreadPool
()
{
return
new
ThreadPoolExecutor
(
0
,
Integer
。
MAX_VALUE
,
60L
,
TimeUnit
。
SECONDS
,
new
SynchronousQueue
<
Runnable
>());
}
3。2。3SingleThreadExecutor
使用單個worker執行緒的Executor
public
static
ExecutorService
newSingleThreadExecutor
()
{
return
new
FinalizableDelegatedExecutorService
(
new
ThreadPoolExecutor
(
1
,
1
,
0L
,
TimeUnit
。
MILLISECONDS
,
new
LinkedBlockingQueue
<
Runnable
>()));
}
3。3構造方法
我們讀完上面的預設實現池還有對應的屬性,再回到構造方法看看
構造方法可以讓我們
自定義(擴充套件)執行緒池
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
;
}
指定核心執行緒數量
指定最大執行緒數量
允許執行緒空閒時間
時間物件
阻塞佇列
執行緒工廠
任務拒絕策略
再總結一遍這些引數的要點:
執行緒數量要點
:
如果執行執行緒的數量
少於
核心執行緒數量,則
建立新
的執行緒處理請求
如果執行執行緒的數量
大於
核心執行緒數量,
小於
最大執行緒數量,則當
佇列滿的時候才建立新
的執行緒
如果核心執行緒數量
等於
最大執行緒數量,那麼將
建立固定大小
的連線池
如果設定了最大執行緒數量為
無窮
,那麼允許執行緒池適合
任意
的併發數量
執行緒空閒時間要點:
當前執行緒數
大於
核心執行緒數,如果空閒時間已經超過了,那該執行緒會
銷燬
。
排隊策略要點
:
同步移交:
不會放到佇列中,而是等待執行緒執行它
。如果當前執行緒沒有執行,很可能會
新開
一個執行緒執行。
無界限策略:
如果核心執行緒都在工作,該執行緒會放到佇列中
。所以執行緒數不會超過核心執行緒數
有界限策略:
可以避免資源耗盡
,但是一定程度上減低了吞吐量
當執行緒關閉或者執行緒數量滿了和佇列飽和了,就有拒絕任務的情況了:
拒絕任務策略:
直接丟擲異常
使用呼叫者的執行緒來處理
直接丟掉這個任務
丟掉最老的任務
四、execute執行方法
execute執行方法分了三步,以註釋的方式寫在程式碼上了~
public
void
execute
(
Runnable
command
)
{
if
(
command
==
null
)
throw
new
NullPointerException
();
int
c
=
ctl
。
get
();
//如果執行緒池中執行的執行緒數量 if ( workerCountOf ( c ) < corePoolSize ) { if ( addWorker ( command , true )) return ; c = ctl 。 get (); } //如果執行緒池中執行的執行緒數量>=corePoolSize,且執行緒池處於RUNNING狀態,且把提交的任務成功放入阻塞佇列中,就再次檢查執行緒池的狀態, // 1。如果執行緒池不是RUNNING狀態,且成功從阻塞佇列中刪除任務,則該任務由當前 RejectedExecutionHandler 處理。 // 2。否則如果執行緒池中執行的執行緒數量為0,則透過addWorker(null, false)嘗試新建一個執行緒,新建執行緒對應的任務為null。 if ( isRunning ( c ) && workQueue 。 offer ( command )) { int recheck = ctl 。 get (); if (! isRunning ( recheck ) && remove ( command )) reject ( command ); else if ( workerCountOf ( recheck ) == 0 ) addWorker ( null , false ); } // 如果以上兩種case不成立,即沒能將任務成功放入阻塞佇列中,且addWoker新建執行緒失敗,則該任務由當前 RejectedExecutionHandler 處理。 else if (! addWorker ( command , false )) reject ( command ); } 五、執行緒池關閉 ThreadPoolExecutor提供了 shutdown() 和 shutdownNow() 兩個方法來關閉執行緒池 shutdown() : shutdownNow(): 區別: 呼叫shutdown()後,執行緒池狀態立刻 變為SHUTDOWN ,而呼叫shutdownNow(),執行緒池狀態 立刻變為STOP 。 shutdown() 等待任務執行完 才中斷執行緒,而shutdownNow() 不等任務執行完 就中斷了執行緒。 六、總結 本篇博文主要簡單地將多執行緒的結構體系過了一篇,講了最常用的ThreadPoolExecutor執行緒池是怎麼使用的~~~ 明天希望可以把死鎖寫出來,敬請期待~~~ 還有剩下的幾個執行緒池(給出了參考資料): ScheduledThreadPoolExecutor blog。csdn。net/panweiwei19… cmsblogs。com/?p=2451 ForkJoinPool blog。csdn。net/panweiwei19… 參考資料: 《Java核心技術卷一》 《Java併發程式設計實戰》 cmsblogs。com/?page_id=11… blog。csdn。net/panweiwei19… zhuanlan。zhihu。com/p/35382932 如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以 關注微信公眾號:Java3y 。 文章的目錄導航 : zhongfucheng。bitcron。com/post/shou-j…