65節(jié)82節(jié),我們用了18篇文章討論并發(fā),本節(jié)進(jìn)行簡要總結(jié)。

多線程開發(fā)有兩個(gè)核心問題,一個(gè)是競爭,另一個(gè)是協(xié)作。競爭會出現(xiàn)線程安全問題,所以,本節(jié)首先總結(jié)線程安全的機(jī)制,然后是協(xié)作的機(jī)制。管理競爭和協(xié)作是復(fù)雜的,所以Java提供了更高層次的服務(wù),比如并發(fā)容器類和異步任務(wù)執(zhí)行服務(wù),我們也會進(jìn)行總結(jié)。本節(jié)綱要如下:

  • 線程安全的機(jī)制

  • 線程的協(xié)作機(jī)制

  • 容器類

  • 任務(wù)執(zhí)行服務(wù)

線程安全的機(jī)制

線程表示一條單獨(dú)的執(zhí)行流,每個(gè)線程有自己的執(zhí)行計(jì)數(shù)器,有自己的棧,但可以共享內(nèi)存,共享內(nèi)存是實(shí)現(xiàn)線程協(xié)作的基礎(chǔ),但共享內(nèi)存有兩個(gè)問題,競態(tài)條件和內(nèi)存可見性,之前章節(jié)探討了解決這些問題的多種思路:

  • 使用synchronized

  • 使用顯式鎖

  • 使用volatile

  • 使用原子變量和CAS

  • 寫時(shí)復(fù)制

  • 使用ThreadLocal

synchronized

synchronized簡單易用,它只是一個(gè)關(guān)鍵字,大部分情況下,放到類的方法聲明上就可以了,既可以解決競態(tài)條件問題,也可以解決內(nèi)存可見性問題。

需要理解的是,它保護(hù)的是對象,而不是代碼,只有對同一個(gè)對象的synchronized方法調(diào)用,synchronized才能保證它們被順序調(diào)用。對于實(shí)例方法,這個(gè)對象是this,對于靜態(tài)方法,這個(gè)對象是類對象,對于代碼塊,需要指定哪個(gè)對象。

另外,需要注意,它不能嘗試獲取鎖,也不響應(yīng)中斷,還可能會死鎖。不過,相比顯式鎖,synchronized簡單易用,JVM也可以不斷優(yōu)化它的實(shí)現(xiàn),應(yīng)該被優(yōu)先使用。

顯式鎖

顯式鎖是相對于synchronized隱式鎖而言的,它可以實(shí)現(xiàn)synchronzied同樣的功能,但需要程序員自己創(chuàng)建鎖,調(diào)用鎖相關(guān)的接口,主要接口是Lock,主要實(shí)現(xiàn)類是ReentrantLock。

相比synchronized,顯式鎖支持以非阻塞方式獲取鎖、可以響應(yīng)中斷、可以限時(shí)、可以指定公平性、可以解決死鎖問題,這使得它靈活的多。

在讀多寫少、讀操作可以完全并行的場景中,可以使用讀寫鎖以提高并發(fā)度,讀寫鎖的接口是ReadWriteLock,實(shí)現(xiàn)類是ReentrantReadWriteLock。

volatile

synchronized和顯式鎖都是鎖,使用鎖可以實(shí)現(xiàn)安全,但使用鎖是有成本的,獲取不到鎖的線程還需要等待,會有線程的上下文切換開銷等。保證安全不一定需要鎖。如果共享的對象只有一個(gè),操作也只是進(jìn)行最簡單的get/set操作,set也不依賴于之前的值,那就不存在競態(tài)條件問題,而只有內(nèi)存可見性問題,這時(shí),在變量的聲明上加上volatile就可以了。

原子變量和CAS

使用volatile,set的新值不能依賴于舊值,但很多時(shí)候,set的新值與原來的值有關(guān),這時(shí),也不一定需要鎖,如果需要同步的代碼比較簡單,可以考慮原子變量,它們包含了一些以原子方式實(shí)現(xiàn)組合操作的方法,對于并發(fā)環(huán)境中的計(jì)數(shù)、產(chǎn)生序列號等需求,考慮使用原子變量而非鎖。

原子變量的基礎(chǔ)是CAS,比較并設(shè)置,一般的計(jì)算機(jī)系統(tǒng)都在硬件層次上直接支持CAS指令。通過循環(huán)CAS的方式實(shí)現(xiàn)原子更新是一種重要的思維,相比synchronized,它是樂觀的,而synchronized是悲觀的,它是非阻塞式的,而synchronized是阻塞式的。CAS是Java并發(fā)包的基礎(chǔ),基于它可以實(shí)現(xiàn)高效的、樂觀、非阻塞式數(shù)據(jù)結(jié)構(gòu)和算法,它也是并發(fā)包中鎖、同步工具和各種容器的基礎(chǔ)。

寫時(shí)復(fù)制

之所以會有線程安全的問題,是因?yàn)槎鄠€(gè)線程并發(fā)讀寫同一個(gè)對象,如果每個(gè)線程讀寫的對象都是不同的,或者,如果共享訪問的對象是只讀的,不能修改,那也就不存在線程安全問題了。

我們在介紹容器類CopyOnWriteArrayList和CopyOnWriteArraySet時(shí)介紹了寫時(shí)復(fù)制技術(shù),寫時(shí)復(fù)制就是將共享訪問的對象變?yōu)橹蛔x的,寫的時(shí)候,再使用鎖,保證只有一個(gè)線程寫,寫的線程不是直接修改原對象,而是新創(chuàng)建一個(gè)對象,對該對象修改完畢后,再原子性地修改共享訪問的變量,讓它指向新的對象。

ThreadLocal

ThreadLocal就是讓每個(gè)線程,對同一個(gè)變量,都有自己的獨(dú)有拷貝,每個(gè)線程實(shí)際訪問的對象都是自己的,自然也就不存在線程安全問題了。

線程的協(xié)作機(jī)制

多線程之間的核心問題,除了競爭,就是協(xié)作。我們在67節(jié)68節(jié)介紹了多種協(xié)作場景,比如生產(chǎn)者/消費(fèi)者協(xié)作模式、主從協(xié)作模式、同時(shí)開始、集合點(diǎn)等。之前章節(jié)探討了協(xié)作的多種機(jī)制:

  • wait/notify

  • 顯式條件

  • 線程的中斷

  • 協(xié)作工具類

  • 阻塞隊(duì)列

  • Future/FutureTask

wait/notify

wait/notify與synchronized配合一起使用,是線程的基本協(xié)作機(jī)制,每個(gè)對象都有一把鎖和兩個(gè)等待隊(duì)列,一個(gè)是鎖等待隊(duì)列,放的是等待獲取鎖的線程,另一個(gè)是條件等待隊(duì)列,放的是等待條件的線程,wait將自己加入條件等待隊(duì)列,notify從條件等待隊(duì)列上移除一個(gè)線程并喚醒,notifyAll移除所有線程并喚醒。

需要注意的是,wait/notify方法只能在synchronized代碼塊內(nèi)被調(diào)用,調(diào)用wait時(shí),線程會釋放對象鎖,被notify/notifyAll喚醒后,要重新競爭對象鎖,獲取到鎖后才會從wait調(diào)用中返回,返回后,不代表其等待的條件就一定成立了,需要重新檢查其等待的條件。

wait/notify方法看上去很簡單,但往往難以理解wait等的到底是什么,而notify通知的又是什么,只能有一個(gè)條件等待隊(duì)列,這也是wait/notify機(jī)制的局限性,這使得對于等待條件的分析變得復(fù)雜,67節(jié)68節(jié)通過多個(gè)例子演示了其用法,這里就不贅述了。

顯式條件

顯式條件與顯式鎖配合使用,與wait/notify相比,可以支持多個(gè)條件隊(duì)列,代碼更為易讀,效率更高,使用時(shí)注意不要將signal/signalAll誤寫為notify/notifyAll。

中斷

Java中取消/關(guān)閉一個(gè)線程的方式是中斷,中斷并不是強(qiáng)迫終止一個(gè)線程,它是一種協(xié)作機(jī)制,是給線程傳遞一個(gè)取消信號,但是由線程來決定如何以及何時(shí)退出,線程在不同狀態(tài)和IO操作時(shí)對中斷有不同的反應(yīng),作為線程的實(shí)現(xiàn)者,應(yīng)該提供明確的取消/關(guān)閉方法,并用文檔清楚描述其行為,作為線程的調(diào)用者,應(yīng)該使用其取消/關(guān)閉方法,而不是貿(mào)然調(diào)用interrupt。

協(xié)作工具類

除了基本的顯式鎖和條件,針對常見的協(xié)作場景,Java并發(fā)包提供了多個(gè)用于協(xié)作的工具類

信號量類Semaphore用于限制對資源的并發(fā)訪問數(shù)。

倒計(jì)時(shí)門栓CountDownLatch主要用于不同角色線程間的同步,比如在"裁判"-"運(yùn)動員"模式中,"裁判"線程讓多個(gè)"運(yùn)動員"線程同時(shí)開始,也可以用于協(xié)調(diào)主從線程,讓主線程等待多個(gè)從線程的結(jié)果。

循環(huán)柵欄CyclicBarrier用于同一角色線程間的協(xié)調(diào)一致,所有線程在到達(dá)柵欄后都需要等待其他線程,等所有線程都到達(dá)后再一起通過,它是循環(huán)的,可以用作重復(fù)的同步。

阻塞隊(duì)列

對于最常見的生產(chǎn)者/消費(fèi)者協(xié)作模式,可以使用阻塞隊(duì)列,阻塞隊(duì)列封裝了鎖和條件,生產(chǎn)者線程和消費(fèi)者線程只需要調(diào)用隊(duì)列的入隊(duì)/出隊(duì)方法就可以了,不需要考慮同步和協(xié)作問題。

阻塞隊(duì)列有普通的先進(jìn)先出隊(duì)列,包括基于數(shù)組的ArrayBlockingQueue和基于鏈表的LinkedBlockingQueue/LinkedBlockingDeque,也有基于堆的優(yōu)先級阻塞隊(duì)列PriorityBlockingQueue,還有可用于定時(shí)任務(wù)的延時(shí)阻塞隊(duì)列DelayQueue,以及用于特殊場景的阻塞隊(duì)列SynchronousQueue和LinkedTransferQueue。

Future/FutureTask

在常見的主從協(xié)作模式中,主線程往往是讓子線程異步執(zhí)行一項(xiàng)任務(wù),獲取其結(jié)果,手工創(chuàng)建子線程的寫法往往比較麻煩,常見的模式是使用異步任務(wù)執(zhí)行服務(wù),不再手工創(chuàng)建線程,而只是提交任務(wù),提交后馬上得到一個(gè)結(jié)果,但這個(gè)結(jié)果不是最終結(jié)果,而是一個(gè)Future,F(xiàn)uture是一個(gè)接口,主要實(shí)現(xiàn)類是FutureTask。

Future封裝了主線程和執(zhí)行線程關(guān)于執(zhí)行狀態(tài)和結(jié)果的同步,對于主線程而言,它只需要通過Future就可以查詢異步任務(wù)的狀態(tài)、獲取最終結(jié)果、取消任務(wù)等,不需要再考慮同步和協(xié)作問題。

容器類

線程安全的容器有兩類,一類是同步容器,另一類是并發(fā)容器。在理解synchronized一節(jié),我們介紹了同步容器。關(guān)于并發(fā)容器,我們介紹了:

同步容器

Collections類中有一些靜態(tài)方法,可以基于普通容器返回線程安全的同步容器,比如:

public static <T> Collection<T> synchronizedCollection(Collection<T> c)public static <T> List<T> synchronizedList(List<T> list)public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

它們是給所有容器方法都加上synchronized來實(shí)現(xiàn)安全的。同步容器的性能比較低,另外,還需要注意一些問題,比如復(fù)合操作和迭代,需要調(diào)用方手工使用synchronized同步,并注意不要同步錯對象。

而并發(fā)容器是專為并發(fā)而設(shè)計(jì)的,線程安全、并發(fā)度更高、性能更高、迭代不會拋出ConcurrentModificationException、很多容器以原子方式支持一些復(fù)合操作。

寫時(shí)拷貝的List和Set

CopyOnWriteArrayList基于數(shù)組實(shí)現(xiàn)了List接口,CopyOnWriteArraySet基于CopyOnWriteArrayList實(shí)現(xiàn)了Set接口,它們采用了寫時(shí)拷貝,適用于讀遠(yuǎn)多于寫,集合不太大的場合。不適用于數(shù)組很大,且修改頻繁的場景。它們是以優(yōu)化讀操作為目標(biāo)的,讀不需要同步,性能很高,但在優(yōu)化讀的同時(shí)就犧牲了寫的性能。

ConcurrentHashMap

HashMap不是線程安全的,在并發(fā)更新的情況下,HashMap的鏈表結(jié)構(gòu)可能形成環(huán),出現(xiàn)死循環(huán),占滿CPU。ConcurrentHashMap是并發(fā)版的HashMap,通過分段鎖和其他技術(shù)實(shí)現(xiàn)了高并發(fā),讀操作完全并行,寫操作支持一定程度的并行,以原子方式支持一些復(fù)合操作,迭代不用加鎖,不會拋出ConcurrentModificationException。

基于SkipList的Map和Set

ConcurrentHashMap不能排序,容器類中可以排序的Map和Set是TreeMapTreeSet,但它們不是線程安全的。Java并發(fā)包中與TreeMap/TreeSet對應(yīng)的并發(fā)版本是ConcurrentSkipListMap和ConcurrentSkipListSet。ConcurrentSkipListMap是基于SkipList實(shí)現(xiàn)的,SkipList稱為跳躍表或跳表,是一種數(shù)據(jù)結(jié)構(gòu),主要操作復(fù)雜度為O(log(N)),并發(fā)版本采用跳表而不是樹,是因?yàn)樘砀子趯?shí)現(xiàn)高效并發(fā)算法。

ConcurrentSkipListMap沒有使用鎖,所有操作都是無阻塞的,所有操作都可以并行,包括寫。與ConcurrentHashMap類似,迭代器不會拋出ConcurrentModificationException,是弱一致的,也直接支持一些原子復(fù)合操作。

各種隊(duì)列

各種阻塞隊(duì)列主要用于協(xié)作,非阻塞隊(duì)列適用于多個(gè)線程并發(fā)使用一個(gè)隊(duì)列的場合,有兩個(gè)非阻塞隊(duì)列,ConcurrentLinkedQueue和ConcurrentLinkedDeque,ConcurrentLinkedQueue實(shí)現(xiàn)了Queue接口,表示一個(gè)先進(jìn)先出的隊(duì)列,ConcurrentLinkedDeque實(shí)現(xiàn)了Deque接口,表示一個(gè)雙端隊(duì)列。它們都是基于鏈表實(shí)現(xiàn)的,都沒有限制大小,是無界的,這兩個(gè)類最基礎(chǔ)的實(shí)現(xiàn)原理是循環(huán)CAS,沒有使用鎖。

任務(wù)執(zhí)行服務(wù)

關(guān)于任務(wù)執(zhí)行服務(wù),我們介紹了:

基本概念

任務(wù)執(zhí)行服務(wù)大大簡化了執(zhí)行異步任務(wù)所需的開發(fā),它引入了一個(gè)"執(zhí)行服務(wù)"的概念,將"任務(wù)的提交"和"任務(wù)的執(zhí)行"相分離,"執(zhí)行服務(wù)"封裝了任務(wù)執(zhí)行的細(xì)節(jié),對于任務(wù)提交者而言,它可以關(guān)注于任務(wù)本身,如提交任務(wù)、獲取結(jié)果、取消任務(wù),而不需要關(guān)注任務(wù)執(zhí)行的細(xì)節(jié),如線程創(chuàng)建、任務(wù)調(diào)度、線程關(guān)閉等。

任務(wù)執(zhí)行服務(wù)主要涉及以下接口:

  • Runnable和Callable:表示要執(zhí)行的異步任務(wù)

  • Executor和ExecutorService:表示執(zhí)行服務(wù)

  • Future:表示異步任務(wù)的結(jié)果

使用者只需要通過ExecutorService提交任務(wù),通過Future操作任務(wù)和結(jié)果即可,不需要關(guān)注線程創(chuàng)建和協(xié)調(diào)的細(xì)節(jié)。

線程池

任務(wù)執(zhí)行服務(wù)的主要實(shí)現(xiàn)機(jī)制是線程池,實(shí)現(xiàn)類是ThreadPoolExecutor,線程池主要由兩個(gè)概念組成,一個(gè)是任務(wù)隊(duì)列,另一個(gè)是工作者線程。任務(wù)隊(duì)列是一個(gè)阻塞隊(duì)列,保存待執(zhí)行的任務(wù)。工作者線程主體就是一個(gè)循環(huán),循環(huán)從隊(duì)列中接受任務(wù)并執(zhí)行。ThreadPoolExecutor有一些重要的參數(shù),理解這些參數(shù)對于合理使用線程池非常重要,78節(jié)對這些參數(shù)進(jìn)行了詳細(xì)介紹,這里就不贅述了。

ThreadPoolExecutor實(shí)現(xiàn)了生產(chǎn)者/消費(fèi)者模式,工作者線程就是消費(fèi)者,任務(wù)提交者就是生產(chǎn)者,線程池自己維護(hù)任務(wù)隊(duì)列。當(dāng)我們碰到類似生產(chǎn)者/消費(fèi)者問題時(shí),應(yīng)該優(yōu)先考慮直接使用線程池,而非重新發(fā)明輪子,自己管理和維護(hù)消費(fèi)者線程及任務(wù)隊(duì)列。

CompletionService

在異步任務(wù)程序中,一種場景是,主線程提交多個(gè)異步任務(wù),然后希望有任務(wù)完成就處理結(jié)果,并且按任務(wù)完成順序逐個(gè)處理,對于這種場景,Java并發(fā)包提供了一個(gè)方便的方法,使用CompletionService,這是一個(gè)接口,它的實(shí)現(xiàn)類是ExecutorCompletionService,它通過一個(gè)額外的結(jié)果隊(duì)列,方便了對于多個(gè)異步任務(wù)結(jié)果的處理。

定時(shí)任務(wù)

異步任務(wù)中,常見的任務(wù)是定時(shí)任務(wù)。在Java中,有兩種方式實(shí)現(xiàn)定時(shí)任務(wù):

  • 使用java.util包中的Timer和TimerTask

  • 使用Java并發(fā)包中的ScheduledExecutorService

Timer有一些需要特別注意的事項(xiàng):

  • 一個(gè)Timer對象背后只有一個(gè)Timer線程,這意味著,定時(shí)任務(wù)不能耗時(shí)太長,更不能是無限循環(huán)

  • 在執(zhí)行任何一個(gè)任務(wù)的run方法時(shí),一旦run拋出異常,Timer線程就會退出,從而所有定時(shí)任務(wù)都會被取消

ScheduledExecutorService的主要實(shí)現(xiàn)類是ScheduledThreadPoolExecutor,它沒有Timer的問題:

  • 它的背后是線程池,可以有多個(gè)線程執(zhí)行任務(wù)

  • 任務(wù)執(zhí)行線程會捕獲任務(wù)執(zhí)行過程中的所有異常,一個(gè)定時(shí)任務(wù)的異常不會影響其他定時(shí)任務(wù)

所以,實(shí)踐中建議使用ScheduledExecutorService。

小結(jié)

針對多線程開發(fā)的兩個(gè)核心問題,競爭和協(xié)作,本節(jié)總結(jié)了線程安全和協(xié)作的多種機(jī)制,針對高層服務(wù),本節(jié)總結(jié)了并發(fā)容器和任務(wù)執(zhí)行服務(wù),它們讓我們在更高的層次上訪問共享的數(shù)據(jù)結(jié)構(gòu),執(zhí)行任務(wù),而避免陷入線程管理的細(xì)節(jié)。到此為止,關(guān)于并發(fā)我們就告一段落了。

與之前章節(jié)一樣,我們的探討都是基于Java 7的,不過Java 7引入了一個(gè)Fork/Join框架,我們沒有討論。Java 8在并發(fā)方面也有一些更新,比如:

  • 引入了CompletableFuture,增強(qiáng)了原來的Future,以便于實(shí)現(xiàn)組合式異步編程

  • ConcurrentHashMap增加了一些新的方法,內(nèi)部實(shí)現(xiàn)也進(jìn)行了優(yōu)化

  • 引入了流的概念,基于Fork/Join框架,可以非常方便的對大量數(shù)據(jù)進(jìn)行并行操作

關(guān)于這些內(nèi)容,我們在探討Java 8的時(shí)候再繼續(xù)討論。

從下一節(jié)開始,我們來探討Java中的一些動態(tài)特性,比如反射、注解、動態(tài)代理等,它們到底是什么呢?

---------------------

并發(fā)相關(guān)原創(chuàng)文章

(65) 線程的基本概念 

(66) 理解synchronized

(67) 線程的基本協(xié)作機(jī)制 (上)

(68) 線程的基本協(xié)作機(jī)制 (下) 

(69) 線程的中斷

(70) 原子變量和CAS

(71) 顯式鎖

(72) 顯式條件

(73) 并發(fā)容器 - 寫時(shí)拷貝的List和Set 

(74) 并發(fā)容器 - ConcurrentHashMap 

(75) 并發(fā)容器 - 基于SkipList的Map和Set

(76) 并發(fā)容器 - 各種隊(duì)列

(77) 異步任務(wù)執(zhí)行服務(wù)

(78) 線程池

(79) 方便的CompletionService 

(80) 定時(shí)任務(wù)的那些坑

(81) 并發(fā)同步協(xié)作工具 

(82) 理解ThreadLocal

----------------

未完待續(xù),查看最新文章,敬請關(guān)注微信公眾號“老馬說編程”(掃描下方二維碼),從入門到高級,深入淺出,老馬和你一起探索Java編程及計(jì)算機(jī)技術(shù)的本質(zhì)。用心原創(chuàng),保留所有版權(quán)。

大數(shù)據(jù)培訓(xùn),云培訓(xùn),數(shù)據(jù)挖掘培訓(xùn),云計(jì)算培訓(xùn),高端軟件開發(fā)培訓(xùn),項(xiàng)目經(jīng)理培訓(xùn)

http://www.cnblogs.com/swiftma/p/6765099.html