你对锁的理解?如何手动模拟一个死锁?
在并發(fā)編程中有兩個(gè)重要的概念:線(xiàn)程和鎖,多線(xiàn)程是一把雙刃劍,它在提高程序性能的同時(shí),也帶來(lái)了編碼的復(fù)雜性,對(duì)開(kāi)發(fā)者的要求也提高了一個(gè)檔次。而鎖的出現(xiàn)就是為了保障多線(xiàn)程在同時(shí)操作一組資源時(shí)的數(shù)據(jù)一致性,當(dāng)我們給資源加上鎖之后,只有擁有此鎖的線(xiàn)程才能操作此資源,而其他線(xiàn)程只能排隊(duì)等待使用此鎖
死鎖是指兩個(gè)線(xiàn)程同時(shí)占用兩個(gè)資源,又在彼此等待對(duì)方釋放鎖資源,如下圖所示
死鎖的代碼演示如下
import?java.util.concurrent.TimeUnit;public?class?LockExample?{public?static?void?main(String[]?args)?{deadLock();?//?死鎖}/***?死鎖*/private?static?void?deadLock()?{Object?lock1?=?new?Object();Object?lock2?=?new?Object();//?線(xiàn)程一擁有?lock1?試圖獲取?lock2new?Thread(()?->?{synchronized?(lock1)?{System.out.println("獲取?lock1?成功");try?{TimeUnit.SECONDS.sleep(3);}?catch?(InterruptedException?e)?{e.printStackTrace();}//?試圖獲取鎖?lock2synchronized?(lock2)?{System.out.println(Thread.currentThread().getName());}}}).start();//?線(xiàn)程二擁有?lock2?試圖獲取?lock1new?Thread(()?->?{synchronized?(lock2)?{System.out.println("獲取?lock2?成功");try?{TimeUnit.SECONDS.sleep(3);}?catch?(InterruptedException?e)?{e.printStackTrace();}//?試圖獲取鎖?lock1synchronized?(lock1)?{System.out.println(Thread.currentThread().getName());}}}).start();} }以上程序執(zhí)行結(jié)果如下
獲取?lock1?成功 獲取?lock2?成功當(dāng)我們使用線(xiàn)程一擁有鎖 lock1 的同時(shí)試圖獲取 lock2,而線(xiàn)程二在擁有 lock2 的同時(shí)試圖獲取 lock1,這樣就會(huì)造成彼此都在等待對(duì)方釋放資源,于是就形成了死鎖
鎖是指在并發(fā)編程中,當(dāng)有多個(gè)線(xiàn)程同時(shí)操作一個(gè)資源時(shí),為了保證數(shù)據(jù)操作的正確性,我們需要讓多線(xiàn)程排隊(duì)一個(gè)一個(gè)地操作此資源,而這個(gè)過(guò)程就是給資源加鎖和釋放鎖的過(guò)程
?
悲觀鎖和樂(lè)觀鎖
悲觀鎖指的是數(shù)據(jù)對(duì)外界的修改采取保守策略,它認(rèn)為線(xiàn)程很容易會(huì)把數(shù)據(jù)修改掉,因此在整個(gè)數(shù)據(jù)被修改的過(guò)程中都會(huì)采取鎖定狀態(tài),直到一個(gè)線(xiàn)程使用完,其他線(xiàn)程才可以繼續(xù)使用
悲觀鎖的實(shí)現(xiàn)流程,以 synchronized 為例,代碼如下
public?class?LockExample?{public?static?void?main(String[]?args)?{synchronized?(LockExample.class)?{System.out.println("lock");}} }用反編譯工具查到的結(jié)果如下
Compiled?from?"LockExample.java" public?class?com.vincent.interview.ext.LockExample?{public?com.vincent.interview.ext.LockExample();Code:0:?aload_01:?invokespecial?#1??????????????????//?Method?java/lang/Object."<init>":()V4:?returnpublic?static?void?main(java.lang.String[]);Code:0:?ldc???????????#2??????????????????//?class?com/vincent/interview/ext/LockExample2:?dup3:?astore_14:?monitorenter?//?加鎖5:?getstatic?????#3??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;8:?ldc???????????#4??????????????????//?String?lock10:?invokevirtual?#5??????????????????//?Method?java/io/PrintStream.println:(Ljava/lang/String;)V13:?aload_114:?monitorexit?//?釋放鎖15:?goto??????????2318:?astore_219:?aload_120:?monitorexit21:?aload_222:?athrow23:?returnException?table:from????to??target?type5????15????18???any18????21????18???any }被 synchronized 修飾的代碼塊,在執(zhí)行之前先使用 monitorenter 指令加鎖,然后在執(zhí)行結(jié)束之后再使用 monitorexit 指令釋放鎖資源,在整個(gè)執(zhí)行期間此代碼都是鎖定的狀態(tài),這就是典型悲觀鎖的實(shí)現(xiàn)流程
樂(lè)觀鎖和悲觀鎖的概念恰好相反,樂(lè)觀鎖認(rèn)為一般情況下數(shù)據(jù)在修改時(shí)不會(huì)出現(xiàn)沖突,所以在數(shù)據(jù)訪(fǎng)問(wèn)之前不會(huì)加鎖,只是在數(shù)據(jù)提交更改時(shí),才會(huì)對(duì)數(shù)據(jù)進(jìn)行檢測(cè)
Java 中的樂(lè)觀鎖大部分都是通過(guò) CAS(Compare And Swap,比較并交換)操作實(shí)現(xiàn)的,CAS 是一個(gè)多線(xiàn)程同步的原子指令,CAS 操作包含三個(gè)重要的信息,即內(nèi)存位置、預(yù)期原值和新值。如果內(nèi)存位置的值和預(yù)期的原值相等的話(huà),那么就可以把該位置的值更新為新值,否則不做任何修改
CAS 可能會(huì)造成 ABA 的問(wèn)題,ABA 問(wèn)題指的是,線(xiàn)程拿到了最初的預(yù)期原值 A,然而在將要進(jìn)行 CAS 的時(shí)候,被其他線(xiàn)程搶占了執(zhí)行權(quán),把此值從 A 變成了 B,然后其他線(xiàn)程又把此值從 B 變成了 A,然而此時(shí)的 A 值已經(jīng)并非原來(lái)的 A 值了,但最初的線(xiàn)程并不知道這個(gè)情況,在它進(jìn)行 CAS 的時(shí)候,只對(duì)比了預(yù)期原值為 A 就進(jìn)行了修改,這就造成了 ABA 的問(wèn)題
以警匪劇為例,假如某人把裝了 10W 現(xiàn)金的箱子放在了家里,幾分鐘之后要拿它去贖人,然而在趁他不注意的時(shí)候,進(jìn)來(lái)了一個(gè)小偷,用空箱子換走了裝滿(mǎn)錢(qián)的箱子,當(dāng)某人進(jìn)來(lái)之后看到箱子還是一模一樣的,他會(huì)以為這就是原來(lái)的箱子,就拿著它去贖人了,這種情況肯定有問(wèn)題,因?yàn)橄渥右呀?jīng)是空的了,這就是 ABA 的問(wèn)題
ABA 的常見(jiàn)處理方式是添加版本號(hào),每次修改之后更新版本號(hào),拿上面的例子來(lái)說(shuō),假如每次移動(dòng)箱子之后,箱子的位置就會(huì)發(fā)生變化,而這個(gè)變化的位置就相當(dāng)于“版本號(hào)”,當(dāng)某人進(jìn)來(lái)之后發(fā)現(xiàn)箱子的位置發(fā)生了變化就知道有人動(dòng)了手腳,就會(huì)放棄原有的計(jì)劃,這樣就解決了 ABA 的問(wèn)題
JDK 在 1.5 時(shí)提供了 AtomicStampedReference 類(lèi)也可以解決 ABA 的問(wèn)題,此類(lèi)維護(hù)了一個(gè)“版本號(hào)” Stamp,每次在比較時(shí)不止比較當(dāng)前值還比較版本號(hào),這樣就解決了 ABA 的問(wèn)題
相關(guān)源碼如下
public?class?AtomicStampedReference<V>?{private?static?class?Pair<T>?{final?T?reference;final?int?stamp;?//?“版本號(hào)”private?Pair(T?reference,?int?stamp)?{this.reference?=?reference;this.stamp?=?stamp;}static?<T>?Pair<T>?of(T?reference,?int?stamp)?{return?new?Pair<T>(reference,?stamp);}}//?比較并設(shè)置public?boolean?compareAndSet(V???expectedReference,V???newReference,int?expectedStamp,?//?原版本號(hào)int?newStamp)?{?//?新版本號(hào)Pair<V>?current?=?pair;returnexpectedReference?==?current.reference?&&expectedStamp?==?current.stamp?&&((newReference?==?current.reference?&&newStamp?==?current.stamp)?||casPair(current,?Pair.of(newReference,?newStamp)));}//.......省略其他源碼 }可以看出它在修改時(shí)會(huì)進(jìn)行原值比較和版本號(hào)比較,當(dāng)比較成功之后會(huì)修改值并修改版本號(hào),樂(lè)觀鎖有一個(gè)優(yōu)點(diǎn),它在提交的時(shí)候才進(jìn)行鎖定的,因此不會(huì)造成死鎖
可重入鎖
可重入鎖也叫遞歸鎖,指的是同一個(gè)線(xiàn)程,如果外面的函數(shù)擁有此鎖之后,內(nèi)層的函數(shù)也可以繼續(xù)獲取該鎖。在 Java 語(yǔ)言中 ReentrantLock 和 synchronized 都是可重入鎖
synchronized 來(lái)演示一下什么是可重入鎖,代碼如下
public?class?LockExample?{public?static?void?main(String[]?args)?{reentrantA();?//?可重入鎖}/***?可重入鎖?A?方法*/private?synchronized?static?void?reentrantA()?{System.out.println(Thread.currentThread().getName()?+?":執(zhí)行?reentrantA");reentrantB();}/***?可重入鎖?B?方法*/private?synchronized?static?void?reentrantB()?{System.out.println(Thread.currentThread().getName()?+?":執(zhí)行?reentrantB");} }以上代碼的執(zhí)行結(jié)果如下
main:執(zhí)行?reentrantA main:執(zhí)行?reentrantBreentrantA 方法和 reentrantB 方法的執(zhí)行線(xiàn)程都是“main” ,我們調(diào)用了 reentrantA 方法,它的方法中嵌套了 reentrantB,如果 synchronized 是不可重入的話(huà),那么線(xiàn)程會(huì)被一直堵塞
可重入鎖的實(shí)現(xiàn)原理:在鎖內(nèi)部存儲(chǔ)了一個(gè)線(xiàn)程標(biāo)識(shí),用于判斷當(dāng)前的鎖屬于哪個(gè)線(xiàn)程,并且鎖的內(nèi)部維護(hù)了一個(gè)計(jì)數(shù)器,當(dāng)鎖空閑時(shí)此計(jì)數(shù)器的值為 0,當(dāng)被線(xiàn)程占用和重入時(shí)分別加 1,當(dāng)鎖被釋放時(shí)計(jì)數(shù)器減 1,直到減到 0 時(shí)表示此鎖為空閑狀態(tài)
共享鎖和獨(dú)占鎖
只能被單線(xiàn)程持有的鎖叫獨(dú)占鎖,可以被多線(xiàn)程持有的鎖叫共享鎖
獨(dú)占鎖指的是在任何時(shí)候最多只能有一個(gè)線(xiàn)程持有該鎖,比如 synchronized 就是獨(dú)占鎖,而 ReadWriteLock 讀寫(xiě)鎖允許同一時(shí)間內(nèi)有多個(gè)線(xiàn)程進(jìn)行讀操作,它就屬于共享鎖
獨(dú)占鎖可以理解為悲觀鎖,當(dāng)每次訪(fǎng)問(wèn)資源時(shí)都要加上互斥鎖,而共享鎖可以理解為樂(lè)觀鎖,它放寬了加鎖的條件,允許多線(xiàn)程同時(shí)訪(fǎng)問(wèn)該資源
總結(jié)
以上是生活随笔為你收集整理的你对锁的理解?如何手动模拟一个死锁?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: citus介绍和centos7安装部署和
- 下一篇: 小偷问题