Thread and Runnable
現代作業系統,都支援透過程序和執行緒來實現併發。程序是程式的執行時的例項。程式是靜態的,而程序是動態的。程序與程序之間,相互獨立。例如,如果你執行一個Java程式,作業系統就會生成一個和其他程序並行執行的程序。而在程序內部,我們又可以透過使用執行緒來併發的執行操作,並充分利用現代機器多核的優勢。
Java從JDK1。0開始,便開始支援執行緒了。我們在啟動一個執行緒之前,必須告訴它要它執行什麼操作。我們可以透過實現** Runnable**介面來告訴執行緒它要執行的操作,如下圖所示:
我們透過lambda表示式來實現了** Runnable
介面.我們讓它做的事是,將執行緒的名稱輸出到控制檯.在我們啟動執行緒之前,我們先直接啟動
Runnable**,看看會發生什麼。
結果應該是這樣:
或者是這樣:
因為新建立的執行緒和主執行緒現在是並行執行,所以我們並不能確定最後的那兩行輸出的順序。而且因為這種順序的不確定性,導致併發程式設計比單執行緒程式設計要困難的多。
我們可以讓執行緒暫停一段時間。我們一般用這來模擬需要執行很長時間的任務的執行緒。
如果你執行上面的程式碼,你會看到,第一條輸出和第二條輸出中間,間隔了一秒。 ** 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**:
** Executors
是一個工廠類,我們可以用它來建立各種各樣的
Executor Service
.這裡建立了一個只有一個執行緒的
Executor Service**。
輸出的結果很容易理解。你可能會注意到,這個程式一直在執行,一直都不停止。實際上,我們需要手動停掉** Executor**,否則它會一直執行,來監聽新的任務。
** ExecutorService
提供了兩個方法,讓我們停掉
Executor
.一個是
shutdown()
,它會等待當前正在執行的任務停止.另一個是
shutdownNow()
,它會中斷全部的正在執行的任務,並讓
Executor**立即停止。
我們使用如下圖所示的這種方式,來停掉** Executor**更好:
** Executor**會等待一段時間,讓正在執行的任務執行完。超過五秒之後,就停掉全部的正在執行的task。
Callables和Futures
除了** Runnable
,
Executor
還接受
Callable
作為引數.
Callable
和
Runnable
的區別在於,
Callable**是有返回值的。
下圖中的** Callable**,會先暫停一秒鐘,然後輸出一個整數:
透過** Callable
來建立
Executor
的方式,和透過
Runnable
來建立
Executor
的方式一樣.那麼我們如何來獲取
Callable
的返回值呢?我們可以透過
Future
物件來獲取.因為
submit()
方法是非阻塞的,它不會等待任務完成,然後直接返回任務的返回值.而是透過返回一個
Future
物件,其中會封裝任務的返回值.等任務完成後,我們就能透過這個
Future
物件,來獲取
Callable**的返回值。
在透過** submit()
方法提交完任務之後,我們透過
isDone()
方法,來檢視
Future**物件是否完成。這裡當然不會,因為執行緒會在返回值之前,先暫停一秒。
呼叫** get()
方法,會阻塞當前執行緒,直到
callable
執行完畢.現在
Future**物件完成了,我們可以在控制檯中,看到如下結果:
** Future
物件和
Executor Service
之間,有輕微的耦合.需要注意的是,如果你在
Future
完成之前,結束
Executor**,那麼它會丟擲異常。
你可能已經注意到了,這裡我們是透過** newFixedThreadPool(1)
方法,來建立的
ExecutorService
,它會建立一個只有一個執行緒的執行緒池.其等價與
newSingleThreadExecutor**。但是我們可以透過調整其引數來改變該執行緒池的大小。
Timeouts
** future。get()
方法,會阻塞當前執行緒,直到
Callable
執行完畢.那如果
Callable**執行的是一個死迴圈呢?這會導致我們的程式失去響應。我們可以透過設定超時時間,來解決這個問題:
上面的程式碼會丟擲** TimeoutException**:
你應該知道為何丟擲這個異常:我們設定其最多等待1秒,但是** Callable**執行卻需要兩秒。
InvokeAll
** Executor Service
支援透過呼叫
invokeAll()
方法,來傳入多個
Callable
,實現一次執行多個任務的目的.這個方法,其引數是
Callable
的集合,其返回值,是
Future**物件的集合。
InvokeAny
另一個一次執行多個任務的方法是** invokeAny()
方法,這個方法和
invokeAll()
方法,有一些不同.這個方法不會返回
Future
物件,它會一直等到第一個
Callable**執行結束,然後返回其返回值。
我們使用下面的幫助類,來生成** Callable**。
然後執行下面的程式碼,它會返回需要執行的時間最短的任務的返回值:
上面的程式碼中,透過** newWorkStealingPool()
來建立了另一種
ExecutorService
.這種
ExecutorService**,其執行緒池中的執行緒的數量,預設為我們的機器的核數。
Scheduled Executors
我們現在已經知道如何來透過** Executor Service**啟動執行緒了。那如果有一個任務,需要重複執行很多次,或者定時執行,那我們該怎麼辦呢?
我們可以使用** Scheduled Executors**。
下面的程式碼,會在三秒後,啟動一個執行緒來執行任務:
** schedule()
方法,會返回一個
ScheduleFuture
,相對於普通的
Future
來說,它增加了一個
getDelay**方法,來檢視還剩多少時間來啟動執行緒執行任務。
** ScheduleExecutorService
提供了
scheduleAtFixedRate()
和
scheduleWithFixedDelay()**這兩個方法。前者會按一定的頻率來執行任務。下面的例子會每秒執行一次任務:
** scheduleAtFixedRate()**方法指定的間隔,不包括任務執行的時間。所以,如果你讓那些需要兩秒來執行的任務,每隔一秒執行一次,執行緒池會很快達到容量上限。
針對上面的那種情況,你應當使用** scheduleWithFixedDelay()**方法。這個方法的引數,是一個任務完成後,再過多久才執行下一個任務。
作者:AlstonWilliams
連結:https://www。jianshu。com/p/590cd048c11c
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。