《Java併發編程實戰》水平很高,然而並非本好書。組織混亂、長篇大論、難以消化,中文翻譯也較死板。這裏是一篇批評此書的帖子,非常貼切。俗話說:「看到有這麼多人罵你,我就放心了」。html
然而知識老是要學的。這裏就總結一下書中及網絡上的內容,做爲Java併發編程之旅的結束,再也不浪費時間了。java
這本書實際上能夠分爲兩個部分。一是多線程的控制,二是併發同步的管理。把它們揉在一塊兒,思路很難清晰。本文就先介紹第一部分,多線程的控制。編程
在Java 5.0以前,多線程編程就是直接操做Thread
。能夠從Thread
類派生一個類,或者實現Runnable
接口的run()
方法,而後調用Thread.start()
啓動線程。數組
線程的幾種狀態:網絡
Java 5.0增長了java.util.concurrent
包,纔有了線程池等強大的工具。多線程
參見Java線程池系列文章。本文略作總結。併發
阻塞隊列,顧名思義,它在基本隊列的基礎上,還有阻塞的功能。即,若是隊列已滿,則入隊操做阻塞等待,直到有空位;若是隊列已空,則出隊操做阻塞等待,直到隊列有元素。相應的方法分別爲put()
和take()
。異步
阻塞隊列有幾種實現:異步編程
就是一個簡單的生產者、消費者模型。線程池中的線程是消費者,循環地從阻塞隊列中提取任務,執行任務。工具
Java線程池的接口是ExecutorService
,它有幾個實現。以ThreadPoolExecutor
爲例,它的使用方式是:
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(5); ExecutorService threadPoolExecutor = new ThreadPoolExecutor( corePoolSize, //初始線程數 maxPoolSize, //最大線程數 keepAliveTime, //空閒線程最大存活時間 TimeUnit.MILLISECONDS, //時間單位 queue // 任務的阻塞隊列 );
要使用這個線程池,可使用它提供的以下方法:
execute(Runnable)
沒有返回值 。submit(Runnable)
返回Future
對象,表明未完成的結果(因爲Runnable
沒有返回值因此內容爲空)。submit(Callable)
返回Future
對象,表明未完成的結果。invokeAny(Collection)
執行全部任務,返回第一個完成的結果。invokeAll(Collection)
執行全部任務,返回Future
對象列表。最後,使用shutdown()
和shutdownNow()
來關閉線程池,中止其中的線程。前者採用後文講到的interrupt方式溫和關閉,後者則調用Thread.stop()
強行關閉。
上面的線程池使用起來仍是太具體了,還須要本身建立線程池,還要本身傳阻塞隊列進去,很差用。因而Java提供了一個幫助類Executors
,很是經常使用。
來看它的經常使用方法:
newFixedThreadPool()
: 建立固定數量的線程池。newCachedThreadPool()
: 建立動態維護線程數的線程池。newSingleThreadExecutor()
: 建立單線程的線程池。Runnable
接口的問題在於沒有返回值,過於簡單了。所以加入了Callable
接口。相比於Runnable
,一是有返回值,二是能夠拋出異常。Future
就是異步編程中對一個尚未完成的任務的抽象,至關於C#中的Task
。一樣有cancel()
、isDone()
等方法,調用get()
則阻塞地獲取結果。FutureTask
是Future
的一個具體實現類,而且不光實現了Future
,還實現了Runnable
接口,使其用舊方式也可調用。具體可見 Runnable、Callable、Future、FutureTask的區別。
這是一個高級話題。Java建議不要用stop()
粗暴地殺死線程,而是採用interrupt()
這種溫和的方式。當線程調用wait()
或sleep()
等阻塞時,對這個線程調用interrupt()
會使線程醒來,並受到InterruptedException
,且線程的中斷標記被設置。如何處理這種狀況取決於線程本身。具體參見這篇文章。