日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

你对锁的理解?如何手动模拟一个死锁?

發(fā)布時間:2024/3/13 编程问答 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 你对锁的理解?如何手动模拟一个死锁? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在并發(fā)編程中有兩個重要的概念:線程和鎖,多線程是一把雙刃劍,它在提高程序性能的同時,也帶來了編碼的復(fù)雜性,對開發(fā)者的要求也提高了一個檔次。而鎖的出現(xiàn)就是為了保障多線程在同時操作一組資源時的數(shù)據(jù)一致性,當(dāng)我們給資源加上鎖之后,只有擁有此鎖的線程才能操作此資源,而其他線程只能排隊等待使用此鎖

死鎖是指兩個線程同時占用兩個資源,又在彼此等待對方釋放鎖資源,如下圖所示

死鎖的代碼演示如下

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();//?線程一擁有?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();//?線程二擁有?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)我們使用線程一擁有鎖 lock1 的同時試圖獲取 lock2,而線程二在擁有 lock2 的同時試圖獲取 lock1,這樣就會造成彼此都在等待對方釋放資源,于是就形成了死鎖

鎖是指在并發(fā)編程中,當(dāng)有多個線程同時操作一個資源時,為了保證數(shù)據(jù)操作的正確性,我們需要讓多線程排隊一個一個地操作此資源,而這個過程就是給資源加鎖和釋放鎖的過程

?

悲觀鎖和樂觀鎖

悲觀鎖指的是數(shù)據(jù)對外界的修改采取保守策略,它認為線程很容易會把數(shù)據(jù)修改掉,因此在整個數(shù)據(jù)被修改的過程中都會采取鎖定狀態(tài),直到一個線程使用完,其他線程才可以繼續(xù)使用

悲觀鎖的實現(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 指令釋放鎖資源,在整個執(zhí)行期間此代碼都是鎖定的狀態(tài),這就是典型悲觀鎖的實現(xiàn)流程

樂觀鎖和悲觀鎖的概念恰好相反,樂觀鎖認為一般情況下數(shù)據(jù)在修改時不會出現(xiàn)沖突,所以在數(shù)據(jù)訪問之前不會加鎖,只是在數(shù)據(jù)提交更改時,才會對數(shù)據(jù)進行檢測

Java 中的樂觀鎖大部分都是通過 CAS(Compare And Swap,比較并交換)操作實現(xiàn)的,CAS 是一個多線程同步的原子指令,CAS 操作包含三個重要的信息,即內(nèi)存位置、預(yù)期原值和新值。如果內(nèi)存位置的值和預(yù)期的原值相等的話,那么就可以把該位置的值更新為新值,否則不做任何修改

CAS 可能會造成 ABA 的問題,ABA 問題指的是,線程拿到了最初的預(yù)期原值 A,然而在將要進行 CAS 的時候,被其他線程搶占了執(zhí)行權(quán),把此值從 A 變成了 B,然后其他線程又把此值從 B 變成了 A,然而此時的 A 值已經(jīng)并非原來的 A 值了,但最初的線程并不知道這個情況,在它進行 CAS 的時候,只對比了預(yù)期原值為 A 就進行了修改,這就造成了 ABA 的問題

以警匪劇為例,假如某人把裝了 10W 現(xiàn)金的箱子放在了家里,幾分鐘之后要拿它去贖人,然而在趁他不注意的時候,進來了一個小偷,用空箱子換走了裝滿錢的箱子,當(dāng)某人進來之后看到箱子還是一模一樣的,他會以為這就是原來的箱子,就拿著它去贖人了,這種情況肯定有問題,因為箱子已經(jīng)是空的了,這就是 ABA 的問題

ABA 的常見處理方式是添加版本號,每次修改之后更新版本號,拿上面的例子來說,假如每次移動箱子之后,箱子的位置就會發(fā)生變化,而這個變化的位置就相當(dāng)于“版本號”,當(dāng)某人進來之后發(fā)現(xiàn)箱子的位置發(fā)生了變化就知道有人動了手腳,就會放棄原有的計劃,這樣就解決了 ABA 的問題

JDK 在 1.5 時提供了 AtomicStampedReference 類也可以解決 ABA 的問題,此類維護了一個“版本號” Stamp,每次在比較時不止比較當(dāng)前值還比較版本號,這樣就解決了 ABA 的問題

相關(guān)源碼如下

public?class?AtomicStampedReference<V>?{private?static?class?Pair<T>?{final?T?reference;final?int?stamp;?//?“版本號”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,?//?原版本號int?newStamp)?{?//?新版本號Pair<V>?current?=?pair;returnexpectedReference?==?current.reference?&&expectedStamp?==?current.stamp?&&((newReference?==?current.reference?&&newStamp?==?current.stamp)?||casPair(current,?Pair.of(newReference,?newStamp)));}//.......省略其他源碼 }

可以看出它在修改時會進行原值比較和版本號比較,當(dāng)比較成功之后會修改值并修改版本號,樂觀鎖有一個優(yōu)點,它在提交的時候才進行鎖定的,因此不會造成死鎖

可重入鎖

可重入鎖也叫遞歸鎖,指的是同一個線程,如果外面的函數(shù)擁有此鎖之后,內(nèi)層的函數(shù)也可以繼續(xù)獲取該鎖。在 Java 語言中 ReentrantLock 和 synchronized 都是可重入鎖

synchronized 來演示一下什么是可重入鎖,代碼如下

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í)行?reentrantB

reentrantA 方法和 reentrantB 方法的執(zhí)行線程都是“main” ,我們調(diào)用了 reentrantA 方法,它的方法中嵌套了 reentrantB,如果 synchronized 是不可重入的話,那么線程會被一直堵塞

可重入鎖的實現(xiàn)原理:在鎖內(nèi)部存儲了一個線程標(biāo)識,用于判斷當(dāng)前的鎖屬于哪個線程,并且鎖的內(nèi)部維護了一個計數(shù)器,當(dāng)鎖空閑時此計數(shù)器的值為 0,當(dāng)被線程占用和重入時分別加 1,當(dāng)鎖被釋放時計數(shù)器減 1,直到減到 0 時表示此鎖為空閑狀態(tài)

共享鎖和獨占鎖

只能被單線程持有的鎖叫獨占鎖,可以被多線程持有的鎖叫共享鎖

獨占鎖指的是在任何時候最多只能有一個線程持有該鎖,比如 synchronized 就是獨占鎖,而 ReadWriteLock 讀寫鎖允許同一時間內(nèi)有多個線程進行讀操作,它就屬于共享鎖

獨占鎖可以理解為悲觀鎖,當(dāng)每次訪問資源時都要加上互斥鎖,而共享鎖可以理解為樂觀鎖,它放寬了加鎖的條件,允許多線程同時訪問該資源

總結(jié)

以上是生活随笔為你收集整理的你对锁的理解?如何手动模拟一个死锁?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。