最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁
在Java并發(fā)場景中,會(huì)涉及到各種各樣的鎖,比如:高并發(fā)編程系列:4種常用Java線程鎖的特點(diǎn),性能比較、使用場景,這些鎖有對應(yīng)的種類:公平鎖,樂觀鎖,悲觀鎖等等,這篇文章來詳細(xì)介紹各種鎖的分類:
公平鎖/非公平鎖
可重入鎖
獨(dú)享鎖/共享鎖
樂觀鎖/悲觀鎖
分段鎖
自旋鎖
樂觀鎖 VS 悲觀鎖
樂觀鎖與悲觀鎖是一種廣義上的概念,體現(xiàn)了看待線程同步的不同角度,在Java和數(shù)據(jù)庫中都有此概念對應(yīng)的實(shí)際應(yīng)用。
1.樂觀鎖
顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù),可以使用版本號等機(jī)制。
樂觀鎖適用于多讀的應(yīng)用類型,樂觀鎖在Java中是通過使用無鎖編程來實(shí)現(xiàn),最常采用的是CAS算法,Java原子類中的遞增操作就通過CAS自旋實(shí)現(xiàn)的。
CAS全稱 Compare And Swap(比較與交換),是一種無鎖算法。在不使用鎖(沒有線程被阻塞)的情況下實(shí)現(xiàn)多線程之間的變量同步。java.util.concurrent包中的原子類就是通過CAS來實(shí)現(xiàn)了樂觀鎖。
簡單來說,CAS算法有3個(gè)三個(gè)操作數(shù):
- 需要讀寫的內(nèi)存值 V。
- 進(jìn)行比較的值 A。
- 要寫入的新值 B。
當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則返回V。這是一種樂觀鎖的思路,它相信在它修改之前,沒有其它線程去修改它;而Synchronized是一種悲觀鎖,它認(rèn)為在它修改之前,一定會(huì)有其它線程去修改它,悲觀鎖效率很低。
2.悲觀鎖
總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。
傳統(tǒng)的MySQL關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。詳情可以參考:阿里P8架構(gòu)師談:MySQL行鎖、表鎖、悲觀鎖、樂觀鎖的特點(diǎn)與應(yīng)用
再比如上面提到的Java的同步synchronized關(guān)鍵字的實(shí)現(xiàn)就是典型的悲觀鎖。
3.總之:
- 悲觀鎖適合寫操作多的場景,先加鎖可以保證寫操作時(shí)數(shù)據(jù)正確。
- 樂觀鎖適合讀操作多的場景,不加鎖的特點(diǎn)能夠使其讀操作的性能大幅提升。
公平鎖 VS 非公平鎖
1.公平鎖
就是很公平,在并發(fā)環(huán)境中,每個(gè)線程在獲取鎖時(shí)會(huì)先查看此鎖維護(hù)的等待隊(duì)列,如果為空,或者當(dāng)前線程是等待隊(duì)列的第一個(gè),就占有鎖,否則就會(huì)加入到等待隊(duì)列中,以后會(huì)按照FIFO的規(guī)則從隊(duì)列中取到自己。
公平鎖的優(yōu)點(diǎn)是等待鎖的線程不會(huì)餓死。缺點(diǎn)是整體吞吐效率相對非公平鎖要低,等待隊(duì)列中除第一個(gè)線程以外的所有線程都會(huì)阻塞,CPU喚醒阻塞線程的開銷比非公平鎖大。
2.非公平鎖
上來就直接嘗試占有鎖,如果嘗試失敗,就再采用類似公平鎖那種方式。
非公平鎖的優(yōu)點(diǎn)是可以減少喚起線程的開銷,整體的吞吐效率高,因?yàn)榫€程有幾率不阻塞直接獲得鎖,CPU不必喚醒所有線程。缺點(diǎn)是處于等待隊(duì)列中的線程可能會(huì)餓死,或者等很久才會(huì)獲得鎖。
3.典型應(yīng)用:
java jdk并發(fā)包中的ReentrantLock可以指定構(gòu)造函數(shù)的boolean類型來創(chuàng)建公平鎖和非公平鎖(默認(rèn)),比如:公平鎖可以使用new ReentrantLock(true)實(shí)現(xiàn)。
獨(dú)享鎖 VS 共享鎖
1.獨(dú)享鎖
是指該鎖一次只能被一個(gè)線程所持有。
2.共享鎖
是指該鎖可被多個(gè)線程所持有。
3.比較
對于Java ReentrantLock而言,其是獨(dú)享鎖。但是對于Lock的另一個(gè)實(shí)現(xiàn)類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨(dú)享鎖。
讀鎖的共享鎖可保證并發(fā)讀是非常高效的,讀寫,寫讀 ,寫寫的過程是互斥的。
獨(dú)享鎖與共享鎖也是通過AQS來實(shí)現(xiàn)的,通過實(shí)現(xiàn)不同的方法,來實(shí)現(xiàn)獨(dú)享或者共享。
4.AQS
抽象隊(duì)列同步器(AbstractQueuedSynchronizer,簡稱AQS)是用來構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架,它使用一個(gè)整型的volatile變量(命名為state)來維護(hù)同步狀態(tài),通過內(nèi)置的FIFO隊(duì)列來完成資源獲取線程的排隊(duì)工作。
concurrent包的實(shí)現(xiàn)結(jié)構(gòu)如上圖所示,AQS、非阻塞數(shù)據(jù)結(jié)構(gòu)和原子變量類等基礎(chǔ)類都是基于volatile變量的讀/寫和CAS實(shí)現(xiàn),而像Lock、同步器、阻塞隊(duì)列、Executor和并發(fā)容器等高層類又是基于基礎(chǔ)類實(shí)現(xiàn)。
分段鎖
分段鎖其實(shí)是一種鎖的設(shè)計(jì),并不是具體的一種鎖,對于ConcurrentHashMap而言,其并發(fā)的實(shí)現(xiàn)就是通過分段鎖的形式來實(shí)現(xiàn)高效的并發(fā)操作。
我們以ConcurrentHashMap來說一下分段鎖的含義以及設(shè)計(jì)思想,ConcurrentHashMap中的分段鎖稱為Segment,它即類似于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í)候,并不是對整個(gè)hashmap進(jìn)行加鎖,而是先通過hashcode來知道他要放在那一個(gè)分段中,然后對這個(gè)分段進(jìn)行加鎖,所以當(dāng)多線程put的時(shí)候,只要不是放在一個(gè)分段中,就實(shí)現(xiàn)了真正的并行的插入。
但是,在統(tǒng)計(jì)size的時(shí)候,可就是獲取hashmap全局信息的時(shí)候,就需要獲取所有的分段鎖才能統(tǒng)計(jì)。
分段鎖的設(shè)計(jì)目的是細(xì)化鎖的粒度,當(dāng)操作不需要更新整個(gè)數(shù)組的時(shí)候,就僅僅針對數(shù)組中的一項(xiàng)進(jìn)行加鎖操作。
你可能也喜歡:
總結(jié)
以上是生活随笔為你收集整理的最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里P8架构师谈:分布式系统全局唯一ID
- 下一篇: Android单元测试研究与实践