雖然我知道你也是一臉懵逼的進(jìn)來(lái)偷办,一臉懵逼的出去澄港,但是等你深入了解后,你會(huì)收益匪淺的回梧,小白可以先收藏起來(lái)
其實(shí)如果按照名稱(chēng)來(lái)說(shuō),鎖大概有以下名詞:
自旋鎖 狱意,自旋鎖的其他種類(lèi),阻塞鎖详囤,可重入鎖 ,讀寫(xiě)鎖蚓再,互斥鎖,悲觀鎖,樂(lè)觀鎖靶庙,公平鎖,偏向鎖, 對(duì)象鎖护姆,線(xiàn)程鎖,鎖粗化卵皂,鎖消除,輕量級(jí)鎖殴玛,重量級(jí)鎖, 信號(hào)量滚粟,獨(dú)享鎖,共享鎖凡壤,分段鎖
我們所說(shuō)的鎖的分類(lèi)其實(shí)應(yīng)該按照鎖的特性和設(shè)計(jì)來(lái)劃分
概述
其實(shí)從并發(fā)的角度來(lái)講,按照線(xiàn)程安全的三種策略看亚侠,主要內(nèi)容都集中在互斥同步里俗扇,我們所討論的鎖也集中在這個(gè)部分。這個(gè)部分的鎖都是悲觀鎖狐援,第二個(gè)部分是非阻塞同步,這個(gè)部分也就一種通過(guò)CAS進(jìn)行原子類(lèi)操作啥酱,這個(gè)部分可以看成樂(lè)觀鎖,其實(shí)也就是不加鎖镶殷。第三個(gè)部分是無(wú)同步方案,包括可重入代碼和線(xiàn)程本地存儲(chǔ)绘趋。
我們這里主要討論的就是互斥同步這一部分。
常見(jiàn)的鎖
Synchronized和Lock
其實(shí)我們真正用到的鎖也就那么兩三種陷遮,只不過(guò)依據(jù)設(shè)計(jì)方案和性質(zhì)對(duì)其進(jìn)行了大量的劃分。
以下一個(gè)鎖是原生語(yǔ)義上的實(shí)現(xiàn)
Synchronized搅方,它就是一個(gè):非公平比吭,悲觀姨涡,獨(dú)享,互斥涛漂,可重入的重量級(jí)鎖
以下兩個(gè)鎖都在JUC包下赏表,是API層面上的實(shí)現(xiàn)
ReentrantLock匈仗,它是一個(gè):默認(rèn)非公平但可實(shí)現(xiàn)公平的,悲觀锚沸,獨(dú)享,互斥前标,可重入,重量級(jí)鎖炼列。
ReentrantReadWriteLocK,它是一個(gè)俭尖,默認(rèn)非公平但可實(shí)現(xiàn)公平的,悲觀稽犁,寫(xiě)?yīng)毾恚x共享已亥,讀寫(xiě)来屠,可重入,重量級(jí)鎖俱笛。
ReentrantLock與synchronized 的區(qū)別
ReentrantLock的高級(jí)操作
中斷等待
ReentrantLock 擁有Synchronized相同的并發(fā)性和內(nèi)存語(yǔ)義,此外還多了 鎖投票迎膜,定時(shí)鎖等候和中斷鎖等候。
線(xiàn)程A和B都要獲取對(duì)象O的鎖定磕仅,假設(shè)A獲取了對(duì)象O鎖镊讼,B將等待A釋放對(duì)O的鎖定
如果使用 synchronized 平夜,如果A不釋放,B將一直等下去忽妒,不能被中斷
如果 使用ReentrantLock,如果A不釋放段直,可以使B在等待了足夠長(zhǎng)的時(shí)間以后,中斷等待鸯檬,而干別的事情
ReentrantLock獲取鎖定有三種方式:
lock(), 如果獲取了鎖立即返回螺垢,如果別的線(xiàn)程持有鎖,當(dāng)前線(xiàn)程則一直處于休眠狀態(tài)枉圃,直到獲取鎖
tryLock(), 如果獲取了鎖立即返回true,如果別的線(xiàn)程正持有鎖孽亲,立即返回false
tryLock(long timeout,TimeUnit unit), 如果獲取了鎖定立即返回true返劲,如果別的線(xiàn)程正持有鎖,會(huì)等待參數(shù)給定的時(shí)間篮绿,在等待的過(guò)程中,如果獲取了鎖定搔耕,就返回true,如果等待超時(shí)弃榨,返回false;
lockInterruptibly:如果獲取了鎖定立即返回娜饵,如果沒(méi)有獲取鎖定,當(dāng)前線(xiàn)程處于休眠狀態(tài)箱舞,直到獲取鎖定遍坟,或者當(dāng)前線(xiàn)程被別的線(xiàn)程中斷
可實(shí)現(xiàn)公平鎖
對(duì)于Java ReentrantLock而言愿伴,通過(guò)構(gòu)造函數(shù)指定該鎖是否是公平鎖,默認(rèn)是非公平鎖隔节。非公平鎖的優(yōu)點(diǎn)在于吞吐量比公平鎖大。
鎖綁定多個(gè)條件
鎖綁定多個(gè)條件是指一個(gè)ReentrantLock對(duì)象可以同時(shí)綁定多個(gè)Condition對(duì)象怎诫,而在synchronized中,鎖對(duì)象的wait()和notify()或notifyAll()方法可以實(shí)現(xiàn)一個(gè)隱含的條件幻妓,如果要和多于一個(gè)的條件關(guān)聯(lián)的時(shí)候劫拢,就不得不額外地添加一個(gè)鎖,而ReentrantLock則無(wú)須這樣做尚镰,只需要多次調(diào)用newCondition()方法即可。
synchronized的優(yōu)勢(shì)
synchronized是在JVM層面上實(shí)現(xiàn)的狗唉,不但可以通過(guò)一些監(jiān)控工具監(jiān)控synchronized的鎖定,而且在代碼執(zhí)行時(shí)出現(xiàn)異常分俯,JVM會(huì)自動(dòng)釋放鎖定,但是使用Lock則不行缸剪,lock是通過(guò)代碼實(shí)現(xiàn)的,要保證鎖定一定會(huì)被釋放杏节,就必須將unLock()放到finally{}中
應(yīng)用場(chǎng)景
在資源競(jìng)爭(zhēng)不是很激烈的情況下,Synchronized的性能要優(yōu)于ReetrantLock镊逝,但是在資源競(jìng)爭(zhēng)很激烈的情況下,Synchronized的性能會(huì)下降幾十倍撑蒜,但是ReetrantLock的性能能維持常態(tài);
按照其性質(zhì)分類(lèi)
公平鎖/非公平鎖
公平鎖是指多個(gè)線(xiàn)程按照申請(qǐng)鎖的順序來(lái)獲取鎖座菠。非公平鎖是指多個(gè)線(xiàn)程獲取鎖的順序并不是按照申請(qǐng)鎖的順序,有可能后申請(qǐng)的線(xiàn)程比先申請(qǐng)的線(xiàn)程優(yōu)先獲取鎖浴滴。有可能,會(huì)造成優(yōu)先級(jí)反轉(zhuǎn)或者饑餓現(xiàn)象巡莹。對(duì)于Java ReentrantLock而言,通過(guò)構(gòu)造函數(shù)指定該鎖是否是公平鎖降宅,默認(rèn)是非公平鎖腰根。非公平鎖的優(yōu)點(diǎn)在于吞吐量比公平鎖大。對(duì)于Synchronized而言额嘿,也是一種非公平鎖。由于其并不像ReentrantLock是通過(guò)AQS的來(lái)實(shí)現(xiàn)線(xiàn)程調(diào)度册养,所以并沒(méi)有任何辦法使其變成公平鎖。
樂(lè)觀鎖/悲觀鎖
樂(lè)觀鎖與悲觀鎖不是指具體的什么類(lèi)型的鎖球拦,而是指看待并發(fā)同步的角度帐我。悲觀鎖認(rèn)為對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作,一定是會(huì)發(fā)生修改的拦键,哪怕沒(méi)有修改,也會(huì)認(rèn)為修改芬为。因此對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作,悲觀鎖采取加鎖的形式媚朦。悲觀的認(rèn)為,不加鎖的并發(fā)操作一定會(huì)出問(wèn)題莲镣。樂(lè)觀鎖則認(rèn)為對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作,是不會(huì)發(fā)生修改的瑞侮。在更新數(shù)據(jù)的時(shí)候鼓拧,會(huì)采用嘗試更新越妈,不斷重新的方式更新數(shù)據(jù)。樂(lè)觀的認(rèn)為梅掠,不加鎖的并發(fā)操作是沒(méi)有事情的。從上面的描述我們可以看出阎抒,悲觀鎖適合寫(xiě)操作非常多的場(chǎng)景,樂(lè)觀鎖適合讀操作非常多的場(chǎng)景且叁,不加鎖會(huì)帶來(lái)大量的性能提升。悲觀鎖在Java中的使用逞带,就是利用各種鎖。樂(lè)觀鎖在Java中的使用穆趴,是無(wú)鎖編程,常常采用的是CAS算法未妹,典型的例子就是原子類(lèi),通過(guò)CAS自旋實(shí)現(xiàn)原子操作的更新教寂。
獨(dú)享鎖/共享鎖
獨(dú)享鎖是指該鎖一次只能被一個(gè)線(xiàn)程所持有。共享鎖是指該鎖可被多個(gè)線(xiàn)程所持有酪耕。對(duì)于Java ReentrantLock而言,其是獨(dú)享鎖迂烁。但是對(duì)于Lock的另一個(gè)實(shí)現(xiàn)類(lèi)ReentrantReadWriteLock,其讀鎖是共享鎖盟步,其寫(xiě)鎖是獨(dú)享鎖躏结。讀鎖的共享鎖可保證并發(fā)讀是非常高效的,讀寫(xiě),寫(xiě)讀 黄橘,寫(xiě)寫(xiě)的過(guò)程是互斥的。獨(dú)享鎖與共享鎖也是通過(guò)AQS來(lái)實(shí)現(xiàn)的塞关,通過(guò)實(shí)現(xiàn)不同的方法,來(lái)實(shí)現(xiàn)獨(dú)享或者共享小压。對(duì)于Synchronized而言,當(dāng)然是獨(dú)享鎖怠益。
互斥鎖/讀寫(xiě)鎖
上面講的獨(dú)享鎖/共享鎖就是一種廣義的說(shuō)法,互斥鎖/讀寫(xiě)鎖就是具體的實(shí)現(xiàn)溉痢。互斥鎖在Java中的具體實(shí)現(xiàn)就是ReentrantLock,讀寫(xiě)鎖在Java中的具體實(shí)現(xiàn)就是ReentrantReadWriteLock
可重入鎖
可重入鎖又名遞歸鎖髓削,是指在同一個(gè)線(xiàn)程在外層方法獲取鎖的時(shí)候,在進(jìn)入內(nèi)層方法會(huì)自動(dòng)獲取鎖立膛。說(shuō)的有點(diǎn)抽象,下面會(huì)有一個(gè)代碼的示例宝泵。對(duì)于Java ReentrantLock而言, 他的名字就可以看出是一個(gè)可重入鎖,其名字是Reentrant Lock重新進(jìn)入鎖儿奶。對(duì)于Synchronized而言,也是一個(gè)可重入鎖〈成樱可重入鎖的一個(gè)好處是可一定程度避免死鎖。
public sychrnozied void test() {
? ? xxxxxx;
? ? test2();
}
public sychronized void test2() {
? ? yyyyy;
}
在上面代碼段中瓤鼻,執(zhí)行 test 方法需要獲得當(dāng)前對(duì)象作為監(jiān)視器的對(duì)象鎖,但方法中又調(diào)用了 test2 的同步方法茬祷。
如果鎖是具有可重入性的話(huà),那么該線(xiàn)程在調(diào)用 test2 時(shí)并不需要再次獲得當(dāng)前對(duì)象的鎖,可以直接進(jìn)入 test2 方法進(jìn)行操作滚停。
如果鎖是不具有可重入性的話(huà),那么該線(xiàn)程在調(diào)用 test2 前會(huì)等待當(dāng)前對(duì)象鎖的釋放铐刘,實(shí)際上該對(duì)象鎖已被當(dāng)前線(xiàn)程所持有,不可能再次獲得镰吵。
如果鎖是不具有可重入性特點(diǎn)的話(huà),那么線(xiàn)程在調(diào)用同步方法疤祭、含有鎖的方法時(shí)就會(huì)產(chǎn)生死鎖。
按照設(shè)計(jì)方案來(lái)分類(lèi)
自旋鎖/自適應(yīng)自旋鎖
在Java中勺馆,自旋鎖是指嘗試獲取鎖的線(xiàn)程不會(huì)立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖侨核,這樣的好處是減少線(xiàn)程上下文切換的消耗,缺點(diǎn)是循環(huán)會(huì)消耗CPU搓译。
鎖粗化/鎖消除
鎖消除是指虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí),對(duì)一些代碼上要求同步豌鸡,但是被檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行消除。鎖消除的主要判定依據(jù)來(lái)源于逃逸分析的數(shù)據(jù)支持涯冠,如果判斷在一段代碼中,堆上的所有數(shù)據(jù)都不會(huì)逃逸出去從而被其他線(xiàn)程訪問(wèn)到逼庞,那就可以把它們當(dāng)做棧上數(shù)據(jù)對(duì)待,認(rèn)為它們是線(xiàn)程私有的往堡,同步加鎖自然就無(wú)須進(jìn)行。
如果一系列的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖虑灰,甚至加鎖操作是出現(xiàn)在循環(huán)體中的,那即使沒(méi)有線(xiàn)程競(jìng)爭(zhēng)穆咐,頻繁地進(jìn)行互斥同步操作也會(huì)導(dǎo)致不必要的性能損耗字旭。如果虛擬機(jī)探測(cè)到有這樣一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖崖叫,將會(huì)把加鎖同步的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部
偏向鎖/輕量級(jí)鎖/重量級(jí)鎖
這三種鎖是指鎖的狀態(tài)沐祷,并且是針對(duì)Synchronized袖外。在Java 5通過(guò)引入鎖升級(jí)的機(jī)制來(lái)實(shí)現(xiàn)高效Synchronized脂男。這三種鎖的狀態(tài)是通過(guò)對(duì)象監(jiān)視器在對(duì)象頭中的字段來(lái)表明的养叛。
偏向鎖是指一段同步代碼一直被一個(gè)線(xiàn)程所訪問(wèn)弃甥,那么該線(xiàn)程會(huì)自動(dòng)獲取鎖。降低獲取鎖的代價(jià)淆攻。
輕量級(jí)鎖是指當(dāng)鎖是偏向鎖的時(shí)候,被另一個(gè)線(xiàn)程所訪問(wèn)嘿架,偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖,其他線(xiàn)程會(huì)通過(guò)自旋的形式嘗試獲取鎖耸彪,不會(huì)阻塞,提高性能搜囱。
重量級(jí)鎖是指當(dāng)鎖為輕量級(jí)鎖的時(shí)候蜀肘,另一個(gè)線(xiàn)程雖然是自旋,但自旋不會(huì)一直持續(xù)下去扮宠,當(dāng)自旋一定次數(shù)的時(shí)候,還沒(méi)有獲取到鎖坛增,就會(huì)進(jìn)入阻塞,該鎖膨脹為重量級(jí)鎖薄腻。重量級(jí)鎖會(huì)讓其他申請(qǐng)的線(xiàn)程進(jìn)入阻塞,性能降低庵楷。
分段鎖
分段鎖其實(shí)是一種鎖的設(shè)計(jì)楣颠,并不是具體的一種鎖咐蚯,對(duì)于ConcurrentHashMap而言,其并發(fā)的實(shí)現(xiàn)就是通過(guò)分段鎖的形式來(lái)實(shí)現(xiàn)高效的并發(fā)操作春锋。我們以ConcurrentHashMap來(lái)說(shuō)一下分段鎖的含義以及設(shè)計(jì)思想,ConcurrentHashMap中的分段鎖稱(chēng)為Segment期奔,它即類(lèi)似于HashMap(JDK7與JDK8中HashMap的實(shí)現(xiàn))的結(jié)構(gòu),即內(nèi)部擁有一個(gè)Entry數(shù)組能庆,數(shù)組中的每個(gè)元素又是一個(gè)鏈表;同時(shí)又是一個(gè)ReentrantLock(Segment繼承了ReentrantLock)搁胆。
當(dāng)需要put元素的時(shí)候,并不是對(duì)整個(gè)hashmap進(jìn)行加鎖渠旁,而是先通過(guò)hashcode來(lái)知道他要放在那一個(gè)分段中,然后對(duì)這個(gè)分段進(jìn)行加鎖粤铭,所以當(dāng)多線(xiàn)程put的時(shí)候,只要不是放在一個(gè)分段中梆惯,就實(shí)現(xiàn)了真正的并行的插入。
但是垛吗,在統(tǒng)計(jì)size的時(shí)候,可就是獲取hashmap全局信息的時(shí)候烁登,就需要獲取所有的分段鎖才能統(tǒng)計(jì)。分段鎖的設(shè)計(jì)目的是細(xì)化鎖的粒度饵沧,當(dāng)操作不需要更新整個(gè)數(shù)組的時(shí)候,就僅僅針對(duì)數(shù)組中的一項(xiàng)進(jìn)行加鎖操作羡儿。