您當前的位置:首頁 > 動漫

執行緒池你真不來了解一下嗎?

作者:由 Java3y 發表于 動漫時間:2018-05-05

前言

只有光頭才能變強

回顧前面:

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…