內(nèi)部鎖
synchronized塊有兩個(gè)部分:鎖對(duì)象的引用以及保護(hù)的代碼塊检碗。
方法的鎖是方法所在的對(duì)象本身瘩缆,靜態(tài)方法的鎖是Class對(duì)象疤估。
每個(gè)java對(duì)象都可以隱式的作為同步的鎖的角色:這些內(nèi)置的鎖被稱為內(nèi)部鎖(intrinsic locks)或者監(jiān)視器鎖(monitor locks)思灌。獲得內(nèi)部鎖的唯一方法就是:進(jìn)入這個(gè)內(nèi)部鎖保護(hù)的方法或者代碼塊。
內(nèi)部鎖在java中扮演了互斥鎖(mutual exclusion locks舌仍,也被稱為mutex)的角色妒貌。
重進(jìn)入(Reentrancy)
內(nèi)部鎖是可以重進(jìn)入的通危,線程請(qǐng)求別的線程的鎖會(huì)拒絕,自己線程的鎖可以獲得灌曙,根據(jù)計(jì)數(shù)器數(shù)量確認(rèn)菊碟,當(dāng)為0時(shí)釋放鎖。
Java監(jiān)視器模式(Java monitor pattern)
對(duì)于遵循Java監(jiān)視器模式的對(duì)象在刺,會(huì)將對(duì)象所有的可變對(duì)象給封閉起來(lái)逆害,并由對(duì)象自己的內(nèi)置鎖進(jìn)行保護(hù)。
/**
* @author fenghongyu
*/
public class PrivateLock {
private Object object = new Object();
private Integer value;
public void addValue(Integer val) {
synchronized (object){
value +=val;
}
}
}
監(jiān)視器模式是一種編碼約定蚣驼,對(duì)于任何一種鎖對(duì)象魄幕,只要自始至終都使用該鎖對(duì)象,都可以用來(lái)保護(hù)對(duì)象的狀態(tài)颖杏。
使用私有對(duì)象鎖纯陨,相比使用公有對(duì)象鎖,有如下好處:
私有的鎖對(duì)象留储,可避免客戶獲取到鎖队丝,僅能通過(guò)方法來(lái)訪問(wèn)鎖,以便合理的參與到它的同步策略中欲鹏。
如果要驗(yàn)證某公有鎖對(duì)象是否被正確使用,需要查詢所有使用到的代碼臭墨,但私有鎖赔嚎,僅需要核對(duì)私有鎖對(duì)象所在的類。
ConcurrentModificationException
對(duì)Collection進(jìn)行迭代的標(biāo)準(zhǔn)方式是使用Iterator胧弛,無(wú)論是顯式的使用還是通過(guò)Java5.0引入的新的for-each循環(huán)語(yǔ)法尤误。當(dāng)存在其他線程并發(fā)修改容器時(shí),使用迭代器(Iterator)不可避免地需要再迭代期間對(duì)容器加鎖结缚。在設(shè)計(jì)同步容器返回的迭代器時(shí)损晤,并沒(méi)有考慮到并發(fā)修改的問(wèn)題,它們是“及時(shí)失敽旖摺(fail-fast)”的尤勋,意思是當(dāng)他們察覺(jué)容器在迭代開(kāi)始后被修改,會(huì)拋出該異常茵宪。
隱藏迭代器
有時(shí)候迭代器是隱藏的最冰,比如是HashSet的toString方法。
死鎖(Deadlock)稀火、饑餓(Starvation)暖哨、活鎖(Livelock)
死鎖,饑餓凰狞,活鎖都屬于多線程情況下的線程活躍性問(wèn)題
死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過(guò)程中篇裁,由于競(jìng)爭(zhēng)資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象沛慢,若無(wú)外力作用,它們都將無(wú)法推進(jìn)下去达布。
此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖团甲,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。
饑餓指的是某一個(gè)或者多個(gè)線程無(wú)法獲得所需要的資源往枣,導(dǎo)致一直無(wú)法執(zhí)行伐庭。
可能的情況包括
線程優(yōu)先級(jí)過(guò)低,高級(jí)的線程不斷搶占它需要的資源
某線程長(zhǎng)時(shí)間占用關(guān)鍵資源不放
與死鎖相比,饑餓是可能在未來(lái)一段時(shí)間內(nèi)解決的(比如高級(jí)線程完成任務(wù),不再瘋狂的執(zhí)行)
活鎖指的是線程都秉承著"謙讓"的原則,主動(dòng)將資源釋放給他人使用,導(dǎo)致資源不斷在多個(gè)線程中跳動(dòng),沒(méi)有一個(gè)線程可以同時(shí)拿到所有的資源而正常執(zhí)行.
ConcurrentHashMap
之前的并發(fā)Hash表使用一個(gè)公共鎖同步每一個(gè)方法分冈,并且嚴(yán)格的限制只能有一個(gè)線程可以同時(shí)訪問(wèn)容器圾另。ConcurrentHashMap使用一個(gè)更加細(xì)化的鎖機(jī)制,叫做分離鎖雕沉。這個(gè)機(jī)制運(yùn)行更深測(cè)的共享訪問(wèn)集乔,任意數(shù)量的讀線程可以并發(fā)訪問(wèn)Map,讀寫(xiě)也可以并發(fā)訪問(wèn)Map坡椒,并且有限數(shù)量的寫(xiě)線程還可以并發(fā)修改Map扰路,為并發(fā)訪問(wèn)帶來(lái)更高的吞吐量,同時(shí)幾乎沒(méi)有損失單個(gè)線程訪問(wèn)的性能倔叼。
ConcurrentHasMap和其他并發(fā)容器一起改進(jìn)了同步容器類汗唱,提供不會(huì)拋出ConcurrentModificationException的迭代器。返回的迭代器具有弱一致性丈攒,而非及時(shí)失敗哩罪。
類似size和isEmpty方法可能并非即時(shí)的。
阻塞隊(duì)列和生產(chǎn)者-消費(fèi)者模式
類庫(kù)中包含一些BlockingQueue的實(shí)現(xiàn)巡验,其中LinkedBlockingQueue和ArrayBlockingQueue是FIFO隊(duì)列际插,PriorityBlockingQueue是一個(gè)按優(yōu)先級(jí)排序的隊(duì)列。最后一個(gè)BlockingQueue的實(shí)現(xiàn)是SynchronousQueue显设。
生產(chǎn)者消費(fèi)者之間要進(jìn)行對(duì)象所有權(quán)安全移交框弛,阻塞隊(duì)列提供了連續(xù)的線程限制來(lái)保證(serial thread confinement)。一個(gè)線程約束的對(duì)象完全由單一線程所有捕捂,但是所有權(quán)可以通過(guò)安全的發(fā)布被“轉(zhuǎn)移”瑟枫,這樣其他的線程中只有唯一一個(gè)能夠得到這個(gè)對(duì)象的訪問(wèn)權(quán),并且保證移交之后原線程不能再訪問(wèn)它指攒。阻塞隊(duì)列可以通過(guò)ConcurrentMap的原子方法remove或者AtomicReference的原子方法compareAndSet來(lái)完成力奋。
雙端隊(duì)列和竊取工作(Deque和BlockQDeque),每一個(gè)消費(fèi)者有自己的雙端隊(duì)列幽七,如果完成之后就去竊取其他消費(fèi)者的雙端隊(duì)列的末端任務(wù)景殷。
阻塞和可中斷的方法
線程會(huì)因?yàn)閹追N原因被阻塞或暫停:等待IO操作結(jié)束,等待獲得一個(gè)鎖,等待從Thread.sleep中喚醒猿挚,或者是等待另一個(gè)線程的計(jì)算結(jié)果咐旧。
當(dāng)一個(gè)線程阻塞時(shí),他通常被掛起绩蜻,并且設(shè)置成線程阻塞的某個(gè)狀態(tài)(BLOCKED铣墨、WAITING、TIMED_WAITING)办绝,等到外部事件的發(fā)生觸發(fā)將線程置回(RUNNABLE)狀態(tài)重新獲得調(diào)度的機(jī)會(huì)伊约。
Synchronizer
Synchronizer是一個(gè)對(duì)象,他根據(jù)本身的狀態(tài)調(diào)節(jié)線程的控制流孕蝉。包含:BlockingQueue(阻塞隊(duì)列)屡律,Semaphore(信號(hào)量)、Barrier(關(guān)卡)降淮、閉鎖(Latch)超埋。
CountDownLatch:比如有一個(gè)任務(wù)A,它要等待其他4個(gè)任務(wù)執(zhí)行完畢之后才能執(zhí)行佳鳖,此時(shí)就可以利用CountDownLatch來(lái)實(shí)現(xiàn)這種功能了霍殴。
CyclicBarrier:假若有若干個(gè)線程都要進(jìn)行寫(xiě)數(shù)據(jù)操作,并且只有所有線程都完成寫(xiě)數(shù)據(jù)操作之后系吩,這些線程才能繼續(xù)做后面的事情来庭,此時(shí)就可以利用CyclicBarrier了
Semaphore:一個(gè)工廠有5臺(tái)機(jī)器,但是有8個(gè)工人穿挨,一臺(tái)機(jī)器同時(shí)只能被一個(gè)工人使用月弛,只有使用完了,其他工人才能繼續(xù)使用絮蒿。那么我們就可以通過(guò)Semaphore來(lái)實(shí)現(xiàn)
FutureTask
FutureTask是一種閉鎖,是通過(guò)Callable實(shí)現(xiàn)的叁鉴,它等價(jià)與一個(gè)可攜帶結(jié)果的Runnable土涝,并且有3個(gè)狀態(tài):等待、運(yùn)行幌墓、完成但壮。完成包括所有方式結(jié)束,正常結(jié)束常侣、取消蜡饵、異常。一旦進(jìn)入完成會(huì)永遠(yuǎn)停在這個(gè)狀態(tài)上胳施。
Future.get的行為依賴于任務(wù)的狀態(tài)溯祸。如果完成可以立刻獲得結(jié)果,否則會(huì)被阻塞到完成為止。
Executor框架
使用有界隊(duì)列防止應(yīng)用程序過(guò)載而耗盡內(nèi)存焦辅。Executor框架提供了一個(gè)靈活的線程池實(shí)現(xiàn)博杖。它為任務(wù)提交和任務(wù)執(zhí)行之間的解耦提供了標(biāo)準(zhǔn)的方法,為使用Runnable描述任務(wù)提供了通用的方法筷登。來(lái)ExecutorService擴(kuò)展了Executor的接口剃根,加入了生命周期管理。生命周期有三種前方,運(yùn)行(Running)狈醉,關(guān)閉(Shutting down)和終止(terminated)。一開(kāi)始是運(yùn)行惠险,shutdown的時(shí)候停止接受新任務(wù)同時(shí)等已經(jīng)提交的任務(wù)完成苗傅,terminate(shutdownNow)會(huì)嘗試取消已經(jīng)提交的任務(wù)。關(guān)閉后提交過(guò)來(lái)的任務(wù)會(huì)被拒絕執(zhí)行處理器(rejected execution handler)處理莺匠。
ExecutorService中所有的submit方法都會(huì)返回一個(gè)Future金吗。
大量互相獨(dú)立且同類的任務(wù)進(jìn)行并發(fā)處理,會(huì)將程序的任務(wù)量分配到不同的任務(wù)中趣竣,這樣才能真正獲得性能的提升摇庙。
CompletionService:Executor遇見(jiàn)BlockingQueue
ExecutorCompletionService,在構(gòu)造函數(shù)中創(chuàng)建一個(gè)BlockingQueue遥缕,用其保存Executor執(zhí)行后的結(jié)果卫袒。
中斷與取消
中斷并不會(huì)真正中斷一個(gè)正在運(yùn)行的線程,它僅僅發(fā)出了一個(gè)中斷請(qǐng)求单匣,線程會(huì)在自己方便的時(shí)候中斷(稱之為中斷點(diǎn)夕凝,cancellation point)。
中斷常是現(xiàn)實(shí)取消最明智的選擇
隊(duì)列飽和
當(dāng)一個(gè)有限隊(duì)列飽和后户秤,飽和策略開(kāi)始起作用码秉。ThreadPoolExecutor的飽和策略可以通過(guò)調(diào)用setRejectedExecutionHandler
減小鎖的粒度
通過(guò)分拆鎖(lock splitting) 分離鎖(lock striping)
分離鎖:
ConcurrentHashMap的實(shí)現(xiàn)使用了一組包含16個(gè)鎖的Array,每一個(gè)鎖都守護(hù)HashBucket的1/16鸡号。
分離鎖的缺點(diǎn)是對(duì)容器加鎖獨(dú)占訪問(wèn)更加困難與昂貴了转砖。例如ConcurrentHashMap的值需要擴(kuò)展、重排鲸伴、放入一個(gè)更大的Bucket中時(shí)府蔗,需要獲取所有分離鎖。
在切換時(shí)汞窗,一個(gè)進(jìn)程存儲(chǔ)在處理器各寄存器中的中間數(shù)據(jù)叫做進(jìn)程的上下文姓赤,所以進(jìn)程的切換實(shí)質(zhì)上就是被中止運(yùn)行進(jìn)程與待運(yùn)行進(jìn)程上下文的切換。在進(jìn)程未占用處理器時(shí)仲吏,進(jìn)程 的上下文是存儲(chǔ)在進(jìn)程的私有堆棧中的不铆。
顯示鎖(Explicit Locks)
Lock和ReentrantLock
Java5.0提供了ReentrantLock蝌焚,這并不是內(nèi)部鎖的替代,而是在內(nèi)部鎖受到局限的時(shí)候提供的高級(jí)特性狂男。與內(nèi)部加鎖機(jī)制不同综看,Lock提供了無(wú)條件、可輪詢岖食、定時(shí)的红碑、可中斷的鎖獲取操作,所有加鎖和解鎖的方法都是顯式的泡垃。Lock的實(shí)現(xiàn)必須提供具有與內(nèi)部加鎖相同的內(nèi)存可見(jiàn)性的語(yǔ)義析珊。
內(nèi)部鎖大部分情況下都能很好的工作,但是有一些功能上的局限:不能中斷目前正在等待獲取鎖的線程蔑穴,并且在請(qǐng)求鎖資源失敗必須無(wú)限等待忠寻。
synchronized在離開(kāi)代碼塊自動(dòng)釋放鎖,ReentrantLock必須顯式地在final釋放鎖存和,這是風(fēng)險(xiǎn)點(diǎn)奕剃。
公平性
ReentrantLock提供了兩種公平性的選擇:創(chuàng)建非公平鎖(默認(rèn))或者非公平鎖。
在你需要:可定時(shí)捐腿、可輪詢纵朋、可中斷的鎖獲取操作,公平隊(duì)列或者非塊結(jié)構(gòu)的鎖的時(shí)候再考慮使用ReentrantLock茄袖,否則請(qǐng)考慮使用synchronized操软。
讀寫(xiě)鎖
ReentrantLock實(shí)現(xiàn)了標(biāo)準(zhǔn)的互斥鎖。ReadWriteLock宪祥,讀寫(xiě)鎖維護(hù)了兩個(gè)鎖聂薪,只讀鎖可以被多個(gè)線程獲取,寫(xiě)鎖只能有一個(gè)線程獲取蝗羊,兩個(gè)鎖是互斥的藏澳。
Java語(yǔ)言的同步機(jī)制在底層實(shí)現(xiàn)上只有兩種手段:"互斥"和"協(xié)同".體現(xiàn)在Java語(yǔ)言層面上,就是內(nèi)置鎖和內(nèi)置條件隊(duì)列.內(nèi)置鎖即synchronized。內(nèi)置條件隊(duì)列這個(gè)詞大家或許沒(méi)有聽(tīng)過(guò),他指的是Object.wait(),Object.notify(),Object.notifyAll()三種方法耀找,就是條件隊(duì)列方法(內(nèi)部條件隊(duì)列的API)翔悠。Java每個(gè)對(duì)象都可以作為鎖,每個(gè)對(duì)象也都能作為條件隊(duì)列涯呻。一個(gè)對(duì)象的內(nèi)部鎖和內(nèi)部條件隊(duì)列是相關(guān)的:為了調(diào)用對(duì)象X的任一個(gè)隊(duì)列方法凉驻,必須要持有對(duì)象X的鎖腻要。除非你能檢查狀態(tài)复罐,否則你不能等待條件;除非你能改變狀態(tài)雄家,否則你不能從條件等待隊(duì)列中釋放其他的線程效诅。
首先隊(duì)列大家一定都清楚,一種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu).正常的隊(duì)列中存儲(chǔ)的都是對(duì)象,而條件隊(duì)列中存儲(chǔ)的是"處于等待狀態(tài)的線程",這些線程在等待某種特定的條件變成真。正如每個(gè)Java對(duì)象都可以作為一個(gè)鎖,每個(gè)對(duì)象同樣可以作為一個(gè)條件隊(duì)列乱投,這個(gè)對(duì)象的wait,notify,notifgAll就構(gòu)成了內(nèi)部條件隊(duì)列的API咽笼。對(duì)象的內(nèi)置鎖與條件隊(duì)列是相互關(guān)聯(lián)的。要調(diào)用條件隊(duì)列的任何一個(gè)方法戚炫,必須先持有該對(duì)對(duì)象上的鎖剑刑。沒(méi)懂的話多讀幾遍,慢慢理解。
我們都知道,線程是用來(lái)干活的,沒(méi)人想讓線程始終處于等待狀態(tài).因此"條件隊(duì)列中的線程一定是執(zhí)行不下去了才處于等待狀態(tài)",這個(gè)"執(zhí)行不下去的條件"叫做"條件謂詞".
至此双肤,我們知道了3個(gè)重要的概念:鎖施掏,條件謂詞,條件隊(duì)列茅糜。雖然他們的關(guān)系并不復(fù)雜七芭,但是一定要注意,wait()方法的返回并不一定意味著正在等待的條件謂詞變成真了蔑赘。舉個(gè)列子:假設(shè)現(xiàn)在有三個(gè)線程在等待同一個(gè)條件謂詞變成真狸驳,然后另外一個(gè)線程調(diào)用了notifyAll()方法。此時(shí)缩赛,只能有一個(gè)線程離開(kāi)條件隊(duì)列耙箍,另外兩個(gè)線程將仍然需要處于等待狀態(tài),這就是在代碼中使用while(conditioin is not true){this.wait();}而不使用if(condition id not true){this.wait();}的原因峦筒。另外一種情況是:同一個(gè)條件隊(duì)列與多個(gè)條件謂詞互相關(guān)聯(lián)究西。這個(gè)時(shí)候,當(dāng)調(diào)用此條件隊(duì)列的notifyAll()方法時(shí)物喷,某些條件謂詞根本就不會(huì)變成真卤材。
剖析Synchronzer
ReentrantLock和Semaphore這兩個(gè)接口有很多共同點(diǎn)。這些類都扮演了“閥門(mén)”的角色峦失,每次只允許有限數(shù)目的線程通過(guò)它扇丛。它們的實(shí)現(xiàn)都使用到一個(gè)共同的基類,AbstractQueuedSynchronizer(AQS)尉辑,包括CountDownLatch帆精,ReentrancReadWriteLock,SynchronousQueue和FutureTask隧魄。AQS解決了實(shí)現(xiàn)一個(gè)Synchronizer的大量細(xì)節(jié)卓练,比如等待線程的FIFO隊(duì)列。
一個(gè)基于AQS的Synchronizer所執(zhí)行的基本操作购啄,是一些不同形式的獲冉笃蟆(acquire)和釋放(release)。為了讓一個(gè)類具有狀態(tài)依賴性狮含,它必須擁有一些狀態(tài)顽悼。同步類中有一些狀態(tài)要管理曼振,AQS管理一個(gè)關(guān)于狀態(tài)信息的單一整數(shù),狀態(tài)信息可以通過(guò)protected類型的getState蔚龙,setState和compareAndSetState等方法進(jìn)行操作冰评。獲取操作可能是獨(dú)占的,像ReentrantLock一樣木羹,也可以是非獨(dú)占的甲雅,像Semaphore和CountDownLatch一樣。
AQS中 維護(hù)了一個(gè)volatile int state(代表共享資源)和一個(gè)FIFO線程等待隊(duì)列(多線程爭(zhēng)用資源被阻塞時(shí)會(huì)進(jìn)入此隊(duì)列)坑填。
原子變量與非阻塞同步機(jī)制
這是java.util.concurrent包中的許多類务荆,例如Semephore和ConcurrentLinkedQueue都提供了比使用synchronized更好的性能和可伸縮性。近來(lái)很多算法的研究都聚焦在非阻塞算法(nonblocking algorithms)上穷遂,這種算法是用低層原子化的機(jī)器指令取代鎖函匕,比如比較并交換(compare-and-swap),保證數(shù)據(jù)在并發(fā)訪問(wèn)下的一致性蚪黑。Java5中的原子變量(atomic variable classes)都能夠高效構(gòu)建非阻塞算法盅惜。原子變量提供了volatile類型變量相同的內(nèi)存語(yǔ)義,同時(shí)還支持額外的原子更新忌穿。