Java8併發教程 - Thread和Executors

Thread and Runnable

現代作業系統,都支援透過程序和執行緒來實現併發。程序是程式的執行時的例項。程式是靜態的,而程序是動態的。程序與程序之間,相互獨立。例如,如果你執行一個Java程式,作業系統就會生成一個和其他程序並行執行的程序。而在程序內部,我們又可以透過使用執行緒來併發的執行操作,並充分利用現代機器多核的優勢。

Java從JDK1。0開始,便開始支援執行緒了。我們在啟動一個執行緒之前,必須告訴它要它執行什麼操作。我們可以透過實現** Runnable**介面來告訴執行緒它要執行的操作,如下圖所示:

Java8併發教程 - Thread和Executors

我們透過lambda表示式來實現了** Runnable

介面.我們讓它做的事是,將執行緒的名稱輸出到控制檯.在我們啟動執行緒之前,我們先直接啟動

Runnable**,看看會發生什麼。

結果應該是這樣:

Java8併發教程 - Thread和Executors

或者是這樣:

Java8併發教程 - Thread和Executors

因為新建立的執行緒和主執行緒現在是並行執行,所以我們並不能確定最後的那兩行輸出的順序。而且因為這種順序的不確定性,導致併發程式設計比單執行緒程式設計要困難的多。

我們可以讓執行緒暫停一段時間。我們一般用這來模擬需要執行很長時間的任務的執行緒。

Java8併發教程 - Thread和Executors

如果你執行上面的程式碼,你會看到,第一條輸出和第二條輸出中間,間隔了一秒。 ** TimeUnit**是一個很有用的列舉,它簡化了對時間單元的處理。

使用Thread類來程式設計,很容易產生錯誤。所以,Java在2004年釋出的JAVA5引入了** Concurrency API

.這些API位於

java。util。concurrent

包中,並且包含很多有用的類.從那之後,這些

Concurrency API**就包含在每個Java版本中了。甚至在Java8中,又為併發引入了新的類和方法。

下面我們來看一下** Concurrency API**中,最重要的那部分- executor services。

Executors

** Concurrency API

打算用

ExecutorService

來取代

Thread

.Executor能夠執行非同步任務,並且管理著一個執行緒池.執行緒池中的執行緒都可以被複用.所以我們只需要透過一個

executor service**,就能執行很多的需要併發執行的任務。

下面這個例子演示瞭如何使用** ExecutorService**:

Java8併發教程 - Thread和Executors

** Executors

是一個工廠類,我們可以用它來建立各種各樣的

Executor Service

.這裡建立了一個只有一個執行緒的

Executor Service**。

輸出的結果很容易理解。你可能會注意到,這個程式一直在執行,一直都不停止。實際上,我們需要手動停掉** Executor**,否則它會一直執行,來監聽新的任務。

** ExecutorService

提供了兩個方法,讓我們停掉

Executor

.一個是

shutdown()

,它會等待當前正在執行的任務停止.另一個是

shutdownNow()

,它會中斷全部的正在執行的任務,並讓

Executor**立即停止。

我們使用如下圖所示的這種方式,來停掉** Executor**更好:

Java8併發教程 - Thread和Executors

** Executor**會等待一段時間,讓正在執行的任務執行完。超過五秒之後,就停掉全部的正在執行的task。

Callables和Futures

除了** Runnable

,

Executor

還接受

Callable

作為引數.

Callable

Runnable

的區別在於,

Callable**是有返回值的。

下圖中的** Callable**,會先暫停一秒鐘,然後輸出一個整數:

Java8併發教程 - Thread和Executors

透過** Callable

來建立

Executor

的方式,和透過

Runnable

來建立

Executor

的方式一樣.那麼我們如何來獲取

Callable

的返回值呢?我們可以透過

Future

物件來獲取.因為

submit()

方法是非阻塞的,它不會等待任務完成,然後直接返回任務的返回值.而是透過返回一個

Future

物件,其中會封裝任務的返回值.等任務完成後,我們就能透過這個

Future

物件,來獲取

Callable**的返回值。

Java8併發教程 - Thread和Executors

在透過** submit()

方法提交完任務之後,我們透過

isDone()

方法,來檢視

Future**物件是否完成。這裡當然不會,因為執行緒會在返回值之前,先暫停一秒。

呼叫** get()

方法,會阻塞當前執行緒,直到

callable

執行完畢.現在

Future**物件完成了,我們可以在控制檯中,看到如下結果:

Java8併發教程 - Thread和Executors

** Future

物件和

Executor Service

之間,有輕微的耦合.需要注意的是,如果你在

Future

完成之前,結束

Executor**,那麼它會丟擲異常。

Java8併發教程 - Thread和Executors

你可能已經注意到了,這裡我們是透過** newFixedThreadPool(1)

方法,來建立的

ExecutorService

,它會建立一個只有一個執行緒的執行緒池.其等價與

newSingleThreadExecutor**。但是我們可以透過調整其引數來改變該執行緒池的大小。

Timeouts

** future。get()

方法,會阻塞當前執行緒,直到

Callable

執行完畢.那如果

Callable**執行的是一個死迴圈呢?這會導致我們的程式失去響應。我們可以透過設定超時時間,來解決這個問題:

Java8併發教程 - Thread和Executors

上面的程式碼會丟擲** TimeoutException**:

Java8併發教程 - Thread和Executors

你應該知道為何丟擲這個異常:我們設定其最多等待1秒,但是** Callable**執行卻需要兩秒。

InvokeAll

** Executor Service

支援透過呼叫

invokeAll()

方法,來傳入多個

Callable

,實現一次執行多個任務的目的.這個方法,其引數是

Callable

的集合,其返回值,是

Future**物件的集合。

Java8併發教程 - Thread和Executors

InvokeAny

另一個一次執行多個任務的方法是** invokeAny()

方法,這個方法和

invokeAll()

方法,有一些不同.這個方法不會返回

Future

物件,它會一直等到第一個

Callable**執行結束,然後返回其返回值。

我們使用下面的幫助類,來生成** Callable**。

Java8併發教程 - Thread和Executors

然後執行下面的程式碼,它會返回需要執行的時間最短的任務的返回值:

Java8併發教程 - Thread和Executors

上面的程式碼中,透過** newWorkStealingPool()

來建立了另一種

ExecutorService

.這種

ExecutorService**,其執行緒池中的執行緒的數量,預設為我們的機器的核數。

Scheduled Executors

我們現在已經知道如何來透過** Executor Service**啟動執行緒了。那如果有一個任務,需要重複執行很多次,或者定時執行,那我們該怎麼辦呢?

我們可以使用** Scheduled Executors**。

下面的程式碼,會在三秒後,啟動一個執行緒來執行任務:

Java8併發教程 - Thread和Executors

** schedule()

方法,會返回一個

ScheduleFuture

,相對於普通的

Future

來說,它增加了一個

getDelay**方法,來檢視還剩多少時間來啟動執行緒執行任務。

** ScheduleExecutorService

提供了

scheduleAtFixedRate()

scheduleWithFixedDelay()**這兩個方法。前者會按一定的頻率來執行任務。下面的例子會每秒執行一次任務:

Java8併發教程 - Thread和Executors

** scheduleAtFixedRate()**方法指定的間隔,不包括任務執行的時間。所以,如果你讓那些需要兩秒來執行的任務,每隔一秒執行一次,執行緒池會很快達到容量上限。

針對上面的那種情況,你應當使用** scheduleWithFixedDelay()**方法。這個方法的引數,是一個任務完成後,再過多久才執行下一個任務。

Java8併發教程 - Thread和Executors

作者:AlstonWilliams

連結:https://www。jianshu。com/p/590cd048c11c

簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。