Java 并發(fā)核心編程
內(nèi)容涉及:
1、關(guān)于java并發(fā)
2、概念
3、保護共享數(shù)據(jù)
4、并發(fā)集合類
5線程
6、線程協(xié)作及其他
1、關(guān)于java并發(fā)
自從java創(chuàng)建以來就已經(jīng)支持并發(fā)的理念,如線程和鎖。這篇指南主要是為幫助java多線程開發(fā)人員理解并發(fā)的核心概念以及如何應(yīng)用這些理念。本文的主題是關(guān)于具有java語言風(fēng)格的Thread、synchronized、volatile,以及J2SE5中新增的概念,如鎖(Lock)、原子性(Atomics)、并發(fā)集合類、線程協(xié)作摘要、Executors。開發(fā)者通過這些基礎(chǔ)的接口可以構(gòu)建高并發(fā)、線程安全的java應(yīng)用程序。
2、概念
本部分描述的java并發(fā)概念在這篇DZone Refard會被通篇使用。
從JVM并發(fā)看CPU內(nèi)存指令重排序(Memory Reordering):http://kenwublog.com/illustrate-memory-reordering-in-cpu
java內(nèi)存模型詳解: http://kenwublog.com/explain-java-memory-model-in-detail
概念 |
描述 |
Java Memory Model Java內(nèi)存模型 |
在JavaSE5(JSR133)中定義的Java Memory Model(JMM)是為了確保當(dāng)編寫并發(fā)代碼的時候能夠提供Java程序員一個可用的JVM實現(xiàn)。術(shù)語JMM的作用類似與一個觀察同步讀寫字段的monitor。它按照“happens-before order(先行發(fā)生排序)”的順序—可以解釋為什么一個線程可以獲得其他線程的結(jié)果,這組成了一個屬性同步的程序,使字段具有不變性,以及其他屬性。 |
monitor Monitor |
Java語言中,每個對象都擁有一個訪問代碼關(guān)鍵部分并防止其他對象訪問這段代碼的“monitor”(每個對象都擁有一個對代碼關(guān)鍵部分提供訪問互斥功能的“monitor”)。這段關(guān)鍵部分是使用synchronized對方法或者代碼標(biāo)注實現(xiàn)的。同一時間在同一個monitor中,只允許一個線程運行代碼的任意關(guān)鍵部分。當(dāng)一個線程試圖獲取代碼的關(guān)鍵部分時,如果這段代碼的monitor被其他線程擁有,那么這個線程會無限期的等待這個monitor直到它被其他線程釋放。除了訪問互斥之外,monitor還可以通過wait和notify來實現(xiàn)協(xié)作。 |
原子字段賦值 Atomic field assignment |
除了doubles和longs之外的類型,給一個這些類型的字段賦值是一個原子操作。在JVM中,doubles和longs的更新是被實現(xiàn)為2個獨立的操作,因此理論上可能會有其他的線程得到一個部分更新的結(jié)果。為了保護共享的doubles和longs,可以使用volatile標(biāo)記這個字段或者在synchronized修飾的代碼塊中操作字段。 |
競爭狀態(tài) Race condition |
競爭發(fā)生在當(dāng)不少于一個線程對一個共享的資源進行一系列的操作,如果這些線程的操作的順序不同,會導(dǎo)致多種可能的結(jié)果。 |
數(shù)據(jù)競爭 Data race |
數(shù)據(jù)競爭主要發(fā)生在多個線程訪問一個共享的、non-final、non-volatile、沒有合適的synchronization限制的字段。Java內(nèi)存模型不會對這種非同步的數(shù)據(jù)訪問提供任何的保證。在不同的架構(gòu)和機器中數(shù)據(jù)競爭會導(dǎo)致不可預(yù)測的行為。 |
安全發(fā)布 Safe publications |
在一個對象創(chuàng)建完成之前就發(fā)布它的引用時非常危險的。避免這種使用這種引用的一種方法就是在創(chuàng)建期間注冊一個回調(diào)接口。另外一種不安全的情況就是在構(gòu)造子中啟動一個線程。在這2種情況中,非完全創(chuàng)建的對象對于其他線程來說都是可見的。 |
不可變字段 Final Fields |
不可變字段在對象創(chuàng)建之后必須明確設(shè)定一個值,否則編譯器就會報出一個錯誤。一旦設(shè)定值后,不可變字段的值就不可以再次改變。將一個對象的引用設(shè)定為不可變字段并不能阻止這個對象的改變。例如,ArrayList類型的不可變字段不能改變?yōu)槠渌?/span>ArrayList實例的引用,但是可以在這個list實例中添加或者刪除對象。 在創(chuàng)建結(jié)尾,對象會遇到”final field freeze”:如果對象被安全的發(fā)布后,即使在沒有synchronization關(guān)鍵字修飾的情況下,也能保證所有的線程獲取final字段在構(gòu)建過程中設(shè)定的值。final field freezer不僅對final字段有用,而且作用于final對象中的可訪問屬性。 |
不可變對象 Immutable objects |
在語法上final 字段能夠創(chuàng)建不需要synchronization修飾的、能夠被共享讀取的線程安全的不可變對象。實現(xiàn)Immutable Object需要保證如下條件: ·對象被安全的發(fā)布(在創(chuàng)建過程中this 引用是無法避免的) ·所有字段被聲明為final ·在創(chuàng)建之后,在對象字段能夠被訪問的范圍中是不允許修改這個字段的。 ·class被聲明為final(為了防止subclass違反這些規(guī)則) |
3、保護共享數(shù)據(jù)
編寫線程安全的java程序,當(dāng)修改共享數(shù)據(jù)的時候要求開發(fā)人員使用合適的鎖來保護數(shù)據(jù)。鎖能夠建立符合Java Memory Model要求的訪問順序,而且確保其他線程知道數(shù)據(jù)的變化。
注意:
在Java Memory Model中,如果沒有被synchronization修飾,改變數(shù)據(jù)不需要什么特別的語法表示。JVM能夠自由地重置指令順序的特性和對可見性的限制方式很容易讓開發(fā)人員感到奇怪。
3.1、Synchronized
每個對象實例都擁有一個每次只能讓一個線程鎖住的monitor。synchronized能夠用在一個方法或者代碼塊中來鎖住這個monitor。用synchronized修飾一個對象,當(dāng)修改這個對象的一個字段,synchronized保證其他線程余下的對這個對象的讀操作能夠獲取修改后的值。需要注意的是修改同步塊之外的數(shù)據(jù)或者synchronized沒有修飾當(dāng)前被修改的對象,那么不能保證其他線程讀到這些最新的數(shù)據(jù)。synchronized關(guān)鍵字能夠修飾一個對象實例中的函數(shù)或者代碼塊。在一個非靜態(tài)方法中this關(guān)鍵字表示當(dāng)前的實例對象。在一個synchronized修飾的靜態(tài)的方法中,這個方法所在的類使用Class作為實例對象。
3.2、Lock
Java.util.concurrent.locks包中有個標(biāo)準(zhǔn)Lock接口。ReentrantLock 實現(xiàn)了Lock接口,它完全擁有synchronized的特性,同時還提供了新的功能:獲取Lock的狀態(tài)、非阻塞獲取鎖的方法tryLock()、可中斷Lock。
下面是使用ReentrantLock的詳細示例:
public class Counter{ private final Lock lock = new ReentrantLock(); private int value; public int increment() { lock.lock(); try { return ++value; }finally{ lock.unlock(); } } } |
3.3、ReadWriteLock
Java.util.concurrent.locks包中還有個ReadWriteLock接口(實現(xiàn)類是ReentrantWriteReadLock),它定義一對鎖:讀鎖和寫鎖,特征是能夠被并發(fā)的讀取但每次只能有一個寫操作。使用ReentrantReadWriteLock并發(fā)讀取特性的詳細示例:
public class ReadWrite { private final ReadWriteLock lock = new ReentrantReadWriteLock(); private int value; public void increment(){ lock.writeLock().lock(); try{ value++; }finally{ lock.writeLock().unlock(); } } public int current(){ lock.readLock().lock(); try{ return value; }finally{ lock.readLock().unlock(); } } } |
3.4、volatile
volatile原理與技巧: http://kenwublog.com/the-theory-of-volatile
volatile修飾符用來標(biāo)注一個字段,表明任何對這個字段的修改都必須能被其他隨后訪問的線程獲取到,這個修飾符和同步無關(guān)。因此,volatile修飾的數(shù)據(jù)的可見性和synchronization類似,但是這個它只作用于對字段的讀或?qū)懖僮?。?/span>JavaSE5之前,因為JVM的架構(gòu)和實現(xiàn)的原因,不同JVM的volatile效果是不同的而且也是不可信的。下面是Java內(nèi)存模型明確地定義volatile的行為:
public class Processor implements Runnable { private volatile boolean stop; public void stopProcessing(){ stop = true; } public void run() { while (!stop) { //do processing } } } |
注意:使用volatile修飾一個數(shù)組并不能讓這個數(shù)組的每個元素擁有volatile特性,這種聲明只是讓這個數(shù)組的reference具有volatile屬性。數(shù)組被聲明為AtomicIntegerArray類型,則能夠擁有類似volatile的特性。
3.5、原子類
使用volatile的一個缺點是它能夠保證數(shù)據(jù)的可見性,卻不能在一個原子操作中對volatile修飾的字段同時進行校驗和更新操作。java.util.concurrent.atomic包中有一系列支持在單個非鎖定(lock)的變量上進行原子操作的類,類似于volatile。示例:
public class Counter{ private AtomicInteger value = new AtomicInteger(); private int value; public int increment() { return value.incrementAndGet(); } } |
incrementAndGet方法是原子類的復(fù)合操作的一個示例。booleans, integers, longs, object references, integers數(shù)組, longs數(shù)組, object references數(shù)組 都有相應(yīng)的原子類。
3.6、ThreadLocal
通過ThreadLocal能數(shù)據(jù)保存在一個線程中,而且不需要lock同步。理論上ThreadLocal可以讓一個變量在每個線程都有一個副本。ThreadLocal常用來屏蔽線程的私有變量,例如“并發(fā)事務(wù)”或者其他的資源。而且,它還被用來維護每個線程的計數(shù)器,統(tǒng)計,或者ID生成器。
public class TransactionManager { private static final ThreadLocal<Transaction> currentTransaction = new ThreadLocal<Transaction>() { @Override protected Transaction initialValue() { return new NullTransaction(); } }; public Transaction currentTransaction() { Transaction current = currentTransaction.get(); if(current.isNull()) { current = new TransactionImpl(); currentTransaction.put(current); } return current; } } |
4、Concurrent Collections(并發(fā)集合類)
保護共享數(shù)據(jù)的一個關(guān)鍵技術(shù)是在存儲數(shù)據(jù)的類中封裝同步機制。所有對數(shù)據(jù)的使用都要經(jīng)過同步機制的確認使這個技術(shù)能夠避免數(shù)據(jù)的不當(dāng)訪問。在java.util.concurrent包中有很多為并發(fā)使用情況下設(shè)計的數(shù)據(jù)結(jié)構(gòu)。通常,使用這些數(shù)據(jù)結(jié)構(gòu)比使用同步包裝器裝飾的非同步的集合的效率更高。
4.1、Concurrent lists and sets
在Table2 中列出了java.util.concurrent包中擁有的3個并發(fā)的List和Set實現(xiàn)類。
類 |
描述 |
CopyOnWriteArraySet |
CopyOnWriteArraySet在語意上提供寫時復(fù)制(copy-on-werite)的特性,對這個集合的每次修改都需要對當(dāng)前數(shù)據(jù)結(jié)構(gòu)新建一個副本,因此寫操作發(fā)費很大。在迭代器創(chuàng)建的時候,會對當(dāng)前數(shù)據(jù)數(shù)據(jù)結(jié)構(gòu)創(chuàng)建一個快照用于迭代。 |
CopyOnWriteArrayList |
CopyOnWriteArrayList和CopyOnWriteArraySet類似,也是基于copy-on-write語義實現(xiàn)了List接口 |
ConcurrentSkipListSet |
ConcurrentSkipListSet(在JavaSE 6新增的)提供的功能類似于TreeSet,能夠并發(fā)的訪問有序的set。因為ConcurrentSkipListSet是基于“跳躍列表(skip list)”實現(xiàn)的,只要多個線程沒有同時修改集合的同一個部分,那么在正常讀、寫集合的操作中不會出現(xiàn)競爭現(xiàn)象。 |
skip list: http://blog.csdn.net/yuanyufei/archive/2007/02/14/1509937.aspx
http://zh.wikipedia.org/zh-cn/%E8%B7%B3%E8%B7%83%E5%88%97%E8%A1%A8
4.2、Concurrent maps
Java.util.concurrent包中有個繼承Map接口的ConcurrentMap的接口,ConcurrentMap提供了一些新的方法(表3)。所有的這些方法在一個原子操作中各自提供了一套操作步驟。如果將每套步驟在放在map之外單獨實現(xiàn),在非原子操作的多線程訪問的情況下會導(dǎo)致資源競爭。
表3:ConcurrentMap的方法:
方法 |
描述 |
putIfAbsent(K key, V value) : V |
如果key在map中不存在,則把key-value鍵值對放入map中,否則不執(zhí)行任何操作。返回值為原來的value,如果key不存在map中則返回null |
remove(Object key, Object value) : boolean |
如果map中有這個key及相應(yīng)的value,那么移除這對數(shù)據(jù),否則不執(zhí)行任何操作 |
replace (K key, V value) : V |
如果map中有這個key,那么用新的value替換原來的value,否則不執(zhí)行任何操作 |
replace (K key, V oldValue, V newValue) : boolean |
如果map中有這對key-oldValue數(shù)據(jù),那么用newValue替換原來的oldValue,否則不執(zhí)行任何操作 |
在表4中列出的是ConcurrentMap的2個實現(xiàn)類
方法 |
描述 |
ConcurrentHashMap |
ConcurrentHashMap提供了2種級別的內(nèi)部哈希方法。第一種級別是選擇一個內(nèi)部的Segment,第二種是在選定的Segment中將數(shù)據(jù)哈希到buckets中。第一種方法通過并行地在不同的Segment上進行讀寫操作來實現(xiàn)并發(fā)。(ConcurrentHashMap是引入了Segment,每個Segment又是一個hash表,ConcurrentHashMap相當(dāng)于是兩級Hash表,然后鎖是在Segment一級進行的,提高了并發(fā)性。http://mooncui.javaeye.com/blog/380884 http://www.javaeye.com/topic/344876 |
ConcurrentSkipListMap |
ConcurrentSkipListMap(JavaSE 6新增的類)功能類似TreeMap,是能夠被并發(fā)訪問的排序map。盡管能夠被多線程正常的讀寫---只要這些線程沒有同時修改map的同一個部分,ConcurrentSkipListMap的性能指標(biāo)和TreeMap差不多。 |
4.3、Queues
Queues類似于溝通“生產(chǎn)者”和“消費者”的管道。組件從管道的一端放入,然后從另一端取出:“先進先出”(FIFO)的順序。Queue接口在JavaSE5新添加到java.util中的,能夠被用于單線程訪問的場景中,主要適用于多個生產(chǎn)者、一個或多個消費者的情景,所有的讀寫操作都是基于同一個隊列。
java.util.concurrent包中的BlockingQueue接口是Queue的子接口,而且還添加了新的特性處理如下場景:隊列滿(此時剛好有一個生產(chǎn)者要加入一個新的組件)、隊列空(此時剛好有一個消費者讀取或者刪除一個組件)。BlockingQueue提供如下方案解決這些情況:一直阻塞等待直到其他線程修改隊列的數(shù)據(jù)狀態(tài);阻塞一段時間之后返回,如果在這段時間內(nèi)有其他線程修改隊列數(shù)據(jù),那么也會返回。
表5:Queue和BlockingQueue的方法:
方法 |
策略 |
插入 |
移除 |
核查 |
Queue |
拋出異常 |
add |
remove |
element |
返回特定的值 |
offer |
poll |
peek | |
Blocking Queue |
一直阻塞 |
put |
take |
n/a |
超時阻塞 |
offer |
poll |
n/a |
在JDK中提供了一些Queue的實現(xiàn),在表6中是這些實現(xiàn)類的關(guān)系列表。
方法 |
描述 |
PriorityQueue |
PriorityQueue是唯一一個非線程安全的隊列實現(xiàn)類,用于單線程存放數(shù)據(jù)并且將數(shù)據(jù)排序。 |
CurrentLinkedQueue |
一個無界的、基于鏈接列表的、唯一一個線程安全的隊列實現(xiàn)類,不支持BlockingQueue。 |
ArrayBlockingQueue |
一個有界的、基于數(shù)組的阻塞隊列。 |
LinkedBlockingQueue |
一個有界的、基于鏈接列表的阻塞隊列。有可能是最常用的隊列實現(xiàn)。 |
PriorityBlockingQueue |
一個無界的、基于堆的阻塞隊列。隊列根據(jù)設(shè)置的Comparator(比較器)來確定組件讀取、移除的順序(不是隊列默認的FIFO順序) |
DelayQueue |
一個無界的、延遲元素(每個延遲元素都會有相應(yīng)的延遲時間值)的阻塞隊列實現(xiàn)。只有在延時期過了之后,元素才能被移除,而且最先被移除的是延時最先到期的元素。 |
SynchronousQueue |
一種0容量的隊列實現(xiàn),生產(chǎn)者添加元素之后必須等待消費者移除后才可以返回,反之依然。如果生產(chǎn)者和消費者2個線程同時訪問,那么參數(shù)直接從生產(chǎn)者傳遞到消費者。經(jīng)常用于線程之間的數(shù)據(jù)傳輸。 |
4.4、Deque
在JavaSE6中新增加了兩端都可以添加和刪除的隊列-Deque (發(fā)音"deck",not "dick"). Deques不僅可以從一端添加元素,從另一端移除,而且兩端都可以添加和刪除元素。如同BlockingQueue,BlockingDeque接口也為阻塞等待和超時等待的特殊情況提供了解決方法。因為Deque繼承Queue、BlockingDeque繼承BlockingQueue,下表中的方法都是可以使用的:
接口 |
頭或尾 |
策略 |
插入 |
移除 |
核查 |
Queue |
Head |
拋出異常 |
addFirst |
removeFirst |
getFirst |
返回特定的值 |
offerFirst |
pollFirst |
peekFirst | ||
Tail |
拋出異常 |
addLast |
removeLast |
getLast | |
返回特定的值 |
offerLast |
pollLast |
peekLast | ||
BlockingQueue |
Head |
一直阻塞 |
putFirst |
takeFirst |
n/a |
超時阻塞 |
offerFirst |
pollFirst |
n/a | ||
Tail |
一直阻塞 |
putLast |
takeLast |
n/a | |
超時阻塞 |
offerLast |
pollLast |
n/a |
Deque的一個特殊應(yīng)用場景是只在一個端口進行添加、刪除、檢查操作--堆棧(first-in-last-out順序)。Deque接口提供了stack相同的方法:push(), pop()和peek(),這方法和addFirst(), removeFirst(), peekFirst()一一對應(yīng),可以把Deque的任何一個實現(xiàn)類當(dāng)做堆棧使用。表6中是JDK中Deque和BlockingDeque的實現(xiàn)。注意Deque繼承Queue,BlockingDeque繼承自BlockingQueue。
表8:Deques
5、線程
在Java中,java.lang.Thread類是用來代表一個應(yīng)用或者JVM線程。代碼是在某個線程類的上下文環(huán)境中執(zhí)行的(使用Thread.currentThread()來獲取當(dāng)前運行的線程)。
5.1、線程通訊
線程之間最簡單的通訊方式是一個線程直接調(diào)用另一個線程對象的方法。表9中列出的是線程之間可以直接交互的方法。
表9:線程協(xié)作方法
類 |
描述 |
LinkedList |
這個經(jīng)常被用到的類在JavaSE6中有了新的改進-實現(xiàn)了Deque接口。在LinkedList中,可以使用標(biāo)準(zhǔn)的Deque方法來添加或者刪除list兩端的元素。LinkedList還可以被當(dāng)做一個非同步的堆棧,用來替代同步的Stack類 |
ArrayDeque |
一個非同步的、支持無限隊列長度(根據(jù)需要動態(tài)擴展隊列的長度)的Deque實現(xiàn)類 |
LinkedBlockingDeque |
LinkeBlockingDeque是Deque實現(xiàn)中唯一支持并發(fā)的、基于鏈接列表、隊列長度可選的類。 |
線程方法 |
描述 |
start |
啟動一個線程實例,并且執(zhí)行它的run() 方法。 |
join |
一直阻塞直到其他線程退出 |
interrupt |
中斷其他線程。線程如果在一個方法中被阻塞,會對interrupt操作做出回應(yīng),并在這個方法執(zhí)行的線程中拋出InterruptedException異常;否則線程的中斷狀態(tài)被設(shè)定。 |
stop, suspend, resume, destroy |
這些方法都被廢棄,不應(yīng)該再使用了。因為線程處理過程中狀態(tài)問題會導(dǎo)致危險的操作。相反,應(yīng)該使用interrupt() 或者 volatile標(biāo)示來告訴一個線程應(yīng)該做什么。 |
5.2、"未捕獲異常"處理器
線程能夠指定一個UncaughtExceptionHandler來接收任何一個導(dǎo)致線程非正常突然終止的未捕獲異常的通知。
5.3、死鎖
當(dāng)存在多個線程(最少2個)等待對方占有的資源,就會形成資源循環(huán)依賴和線程等待,產(chǎn)生死鎖。最常見的導(dǎo)致死鎖的資源是對象monitor,同時其他阻塞操作(例如wait/notify)也能導(dǎo)致死鎖。
很多新的JVM能夠檢測Monitor死鎖,并且可以將線程 dump中由信號(中斷信號)、jstack或者其他線程dump工具生成的死鎖原因顯示打印出來。
除了死鎖,線程之間還會出現(xiàn)饑餓(starvation)和活鎖(livelock). Starvation是因為一個線程長時間占有一個鎖導(dǎo)致其他的線程一直處于等待狀態(tài)無法進行下一步操作。Livelock是因為線程發(fā)費大量的時間來協(xié)調(diào)資源的訪問或者檢測避免死鎖導(dǎo)致沒有一個線程真正的干活。
6、線程協(xié)作
6.1、wait/notify
wait/notify關(guān)鍵字適用于一個線程通知另一個線程所需的條件狀態(tài)已就緒,最常用于線程在循環(huán)中休眠直到獲取特定條件的場景. 例如,一個線程一直等待直到隊列中有一個組件能夠處理;當(dāng)組件添加到隊列時,另一個線程能夠通知這個等待的線程。
wait和notify的經(jīng)典用法是:
Thread t = new Thread(runnable); t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { // TODO get Logger and log uncaught exception } }); t.start();
|
public class Latch { private final Object lock = new Object(); private volatile boolean flag = false; public void waitTillChange(){ synchronized (lock) { while(!flag){ try { lock.wait(); } catch (InterruptedException e) { } } } } public void change(){ synchronized (lock) { flag = true; lock.notifyAll(); } } } |
在代碼中需要注意的重要地方是:
l wait、notify、notifyAll必須在synchronized修飾的代碼塊中執(zhí)行,否則會在運行的時候拋出IllegalMonitorStateException異常
l 在循環(huán)語句wait的時候一定要設(shè)定循環(huán)的條件--這樣能夠避免wait開始之前,線程所需的條件已經(jīng)被其他線程提供了卻依然開始此線程wait導(dǎo)致的時間消耗。同時,這種辦法還能夠保證你的代碼不被虛假的信息喚醒。
l 總是要保證在調(diào)用notify和notifyAll之前,能夠提供符合線程退出等待的條件。否則會出現(xiàn)即使線程接收到通知信息,卻不能退出循環(huán)等待的情況。
6.2、Condition
在JavaSE5中新添加了java.util.concurrent.locks.Condition接口。Condition不僅在API中實現(xiàn)了wait/notify語義,而且提供了幾個新的特性,例如:為每個Lock創(chuàng)建多重Condition、可中斷的等待、訪問統(tǒng)計信息等。Condition是通過Lock示例產(chǎn)生的,示例:
public class LatchCondition { private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private volatile boolean flag = false;
public void waitTillChange(){ lock.lock(); try{ while(!flag){ try { condition.await(); } catch (InterruptedException e) { } } }finally{ lock.unlock(); } } public void change(){ lock.lock(); try{ flag = true; condition.notifyAll(); }finally{ lock.unlock(); } } } |
6.3、Coordination classes
java.util.concurrent包中有幾個類適用于常見的多線程通訊。這幾個協(xié)作類適用范圍幾乎涵蓋了使用wait/notify和Condition最常見的場景,而且更安全、更易于使用。
CyclicBarrier
在CyclicBarrier初始化的時候指定參與者的數(shù)量。參與者調(diào)用awart()方法進入阻塞狀態(tài)直到參與者的個數(shù)達到指定數(shù)量,此時最后一個到達的線程執(zhí)行預(yù)定的屏障任務(wù),然后釋放所有的線程。屏障可以被重復(fù)的重置狀態(tài)。常用于協(xié)調(diào)分組的線程的啟動和停止。
CountDownLatch
需要指定一個計數(shù)才能初始化CountDownLatch。線程調(diào)用await()方法進入等待狀態(tài)知道計數(shù)變?yōu)?/span>0。其他的線程(或者同一個線程)調(diào)用countDown()來減少計數(shù)。如果計數(shù)變?yōu)?/span>0后是無法被重置的。常用于當(dāng)確定數(shù)目的操作完成后,觸發(fā)數(shù)量不定的線程。
Semaphore
Semaphore維護一個“許可”集,能夠使用acquire()方法檢測這個“許可”集,在“許可”可用之前Semaphore會阻塞每個acquire訪問。線程能夠調(diào)用release()來返回一個許可。當(dāng)Semaphore只有一個“許可”的時候,可當(dāng)做一個互斥鎖來使用。
Exchanger
線程在Exchanger的exchange()方法上進行交互、原子操作的方式交換數(shù)據(jù)。功能類似于數(shù)據(jù)可以雙向傳遞的SynchronousQueue加強版。
7、任務(wù)執(zhí)行
很多java并發(fā)程序需要一個線程池來執(zhí)行隊列中的任務(wù)。在java.util.concurrent包中為這種類型的任務(wù)管理提供了一種可靠的基本方法。
7.1、ExecutorService
Executor和易擴展的ExecutorService接口規(guī)定了用于執(zhí)行任務(wù)的組件的標(biāo)準(zhǔn)。這些接口的使用者可以通過一個標(biāo)準(zhǔn)的接口使用各種具有不同行為的實現(xiàn)類。
最通用的Executor接口只能訪問這種類型的可執(zhí)行(Runnable)任務(wù) :
void execute(Runnable command)
Executor子接口ExecutorService新加了方法,能夠執(zhí)行:Runnable任務(wù)、Callable任務(wù)以及任務(wù)集合。
Future<?> submit(Runnable task)
Future<T> submit(Callable<T> task)
Future<T> submit(Runnable task, T result)
List<Future<T>> invokeAll (Collection<? extends Callable<T>> tasks)
List<Future<T>> invokeAll (Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
T invokeAny(Collection<? extends Callable<T>> tasks)
T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
7.2、Callable and Future
Callable類似于Runnable,而且能夠返回值、拋出異常:
l V call() throws Exception;
在一個任務(wù)執(zhí)行框架中提交一個Callable 任務(wù),然后返回一個Future結(jié)果是很常見的。Future表示在將來的某個時刻能夠獲取到結(jié)果。Future提供能夠獲取結(jié)果或者阻塞直到結(jié)果可用的方法。任務(wù)運行之前或正在運行的時候,可以通過Future中的方法取消。
如果只是需要一個Runnable特性的Future(例如在Executor執(zhí)行),可用使用FutureTask。FutureTask實現(xiàn)了Future和Runnable接口,可用提交一個Runnable類型任務(wù),然后在調(diào)用部分使用這個Future類型的任務(wù)。
7.3、實現(xiàn)ExecutorService
ExecutorService最主要的實現(xiàn)類是ThreadPoolExecutor。這個實現(xiàn)類提供了大量的可配置特性:
l 線程池--設(shè)定常用線程數(shù)量(啟動前可選參數(shù))和最大可用線程數(shù)量。
l 線程工廠--通過自定義的線程工廠生成線程,例如生成自定義線程名的線程。
l 工作隊列--指定隊列的實現(xiàn)類,實現(xiàn)類必須是阻塞的、可以是無界的或有界的。
l 被拒絕的任務(wù)--當(dāng)隊列已經(jīng)滿了或者是執(zhí)行者不可用,需要為這些情況指定解決策略。
l 生命周期中的鉤子--重寫擴展在任務(wù)運行之前或之后的生命周期中的關(guān)鍵點
l 關(guān)閉--停止已接受的任務(wù),等待正在運行的任務(wù)完成后,關(guān)閉ThreadPoolExecutor。
ScheduledThreadPoolExecutor是ThreadPoolExecutor的一個子類,能夠按照定時的方式完成任務(wù)(而不是FIFO方式)。在java.util.Timer不是足夠完善的情況下,ScheduleThreadPoolExecutor具有強大的可適用性。
Executors類有很多靜態(tài)方法(表10)用于創(chuàng)建適用于各種常見情況的預(yù)先包裝的ExecutorService和ScheduleExecutorService實例
表10
方法 |
描述 |
newSingleThreadExecutor |
創(chuàng)建只有一個線程的ExecutorService |
newFixedThreadPool |
返回擁有固定數(shù)量線程的ExecutorService |
newCachedThreadPool |
返回一個線程數(shù)量可變的ExecutorService |
newSingleThreadScheduledExecutor |
返回只有一個線程的ScheduledExecutorService |
newScheduledThreadPool |
創(chuàng)建擁有一組核心線程的ScheduledExecutorService |
下面的例子是創(chuàng)建一個固定線程池,然后提交一個長期運行的任務(wù):
在這個示例中提交任務(wù)到executor之后,代碼沒有阻塞而是立即返回。在代碼的最后一行調(diào)用get()方法會阻塞直到有結(jié)果返回。
ExecutorService幾乎涵蓋了所有應(yīng)該創(chuàng)建線程對象或線程池的情景。在代碼中需要直接創(chuàng)建一個線程的時候,可以考慮通過Executor工廠創(chuàng)建的ExecutorService能否實現(xiàn)相同的目標(biāo);這樣做經(jīng)常更簡單、更靈活。
7.4、CompletionService
除了常見的線程池和輸入隊列模式,還有一種常見的情況:為后面的處理,每個任務(wù)生成的結(jié)果必須積累下來。CompletionService接口允許提交Callable和Runnable任務(wù),而且還可以從任務(wù)隊列中獲取這些結(jié)果:(綠色部分和英文版不一樣,已和作者確認,英文版將take()和poll()方法混淆了)
l Future<V> take () -- 如果結(jié)果存在則獲取,否則直接返回
l Future<V> poll () -- 阻塞直到結(jié)果可用
l Future<V> poll (long timeout, TimeUnit unit) -- 阻塞直到timeout時間結(jié)束
ExecutorCompletionService是CompletionService的標(biāo)準(zhǔn)實現(xiàn)類。在ExecutorCompletionService的構(gòu)成函數(shù)中需要一個Executor,ExecutorCompletionService提供輸入隊列和線程池。
8、Hot Tip
熱門信息:當(dāng)設(shè)置線程池大小的時候,最好是基于當(dāng)前應(yīng)用所運行的機器擁有的邏輯處理器的數(shù)量。在java中,可用使用Runtime.getRuntime().availableProcessors()獲取這個值。在JVM的生命周期中,可用處理器的數(shù)目是可變的。
9、關(guān)于作者
Alex Miller是Terracotta Inc公司Java集群開源產(chǎn)品的技術(shù)負責(zé)人,曾在BEA System和MetaMatrix工作,是MetaMatrix的首席架構(gòu)師。他對Java、并發(fā)、分布式系統(tǒng)、查詢語言和軟件設(shè)計感興趣。他的tweeter:@puredanger,blog:http://tect.puredanger.com,很喜歡在用戶組會議中發(fā)言。在St. Louis,Alex是Lambda Lounge小組的創(chuàng)建人,Lambda Lounge用戶組是為了學(xué)習(xí)、動態(tài)語言、Strange Loop開發(fā)會議而創(chuàng)建的。
10、翻譯后記
開始閱讀英文版的時候,并沒有覺得文章中有什么晦澀的地方。但是在翻譯之后,才發(fā)現(xiàn)將文中的意思清楚地表達出來也是個腦力活,有時一句話能夠懂得意思,卻是很難用漢語表達出來:“只可意會,不可言傳”--這也能解釋我當(dāng)年高中作文為啥每次只能拿40分(總分60)。在禪宗,師傅教弟子佛理,多靠弟子自身的明悟,故有當(dāng)頭棒喝、醍醐灌頂之說。做翻譯卻不能這樣,總不能讓讀者對著滿篇的鳥文去琢磨明悟吧,須得直譯、意譯并用,梳理文字。
翻譯也是一個學(xué)習(xí)的過程。閱讀本文的時候會無意忽略自己以為不重要的詞句,待到真正翻譯的時候,才發(fā)現(xiàn)自己一知半解、一竅不通,就只好Google之,翻譯完成后,也學(xué)了些知識,可謂是一箭雙雕。
個人精力所限,翻譯中難免有不對的地方,望大家予以指正。