乐观锁和悲观锁_什么是悲观锁和乐观锁?
思維導(dǎo)圖
文章已收錄Github精選,歡迎Star:https://github.com/yehongzhi/learningSummary悲觀鎖
悲觀鎖是平時(shí)開(kāi)發(fā)中經(jīng)常用到的一種鎖,比如ReentrantLock和synchronized等就是這種思想的體現(xiàn),它總是假設(shè)別的線程在拿線程的時(shí)候都會(huì)修改數(shù)據(jù),所以每次拿到數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別的線程想拿這個(gè)數(shù)據(jù)就會(huì)被阻塞。如圖所示:
synchronized是悲觀鎖的一種實(shí)現(xiàn),一般我們都會(huì)有這樣使用:
private static Object monitor = new Object();public static void main(String[] args) throws Exception {//鎖一段代碼塊synchronized (monitor){} } //鎖實(shí)例方法,鎖對(duì)象是this,即該類實(shí)例本身 public synchronized void doSome(){} //鎖靜態(tài)方法,鎖對(duì)象是該類,即XXX.class public synchronized static void add(){}我們以最簡(jiǎn)單的同步代碼塊來(lái)分析,其實(shí)就是將synchronized作用于一個(gè)給定的實(shí)例對(duì)象monitor,即當(dāng)前實(shí)例對(duì)象就是鎖對(duì)象,每次當(dāng)線程進(jìn)入synchronized包裹的代碼塊時(shí)就會(huì)要求當(dāng)前線程持有monitor實(shí)例對(duì)象鎖,如果當(dāng)前有其他線程正持有該對(duì)象鎖,那么新到的線程就必須等待,這樣也就保證了每次只有一個(gè)線程執(zhí)行synchronized內(nèi)包裹的代碼塊。
從上面的分析中可以看出,悲觀鎖是獨(dú)占和排他的,只要操作資源都會(huì)對(duì)資源進(jìn)行加鎖。假設(shè)讀多寫(xiě)少的情況下,使用悲觀鎖的效果就不是很好。這時(shí)就引出了接下來(lái)要講的樂(lè)觀鎖。
樂(lè)觀鎖
樂(lè)觀鎖,顧名思義它總是假設(shè)最好的情況,線程每次去拿數(shù)據(jù)時(shí)都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒(méi)有去更新這個(gè)數(shù)據(jù),如果這個(gè)數(shù)據(jù)沒(méi)有被更新,當(dāng)前線程將自己修改的數(shù)據(jù)成功寫(xiě)入。如果數(shù)據(jù)已經(jīng)被其他線程更新,則根據(jù)不同的實(shí)現(xiàn)方式執(zhí)行不同的操作(例如報(bào)錯(cuò)或者自動(dòng)重試)。如圖所示:
一般樂(lè)觀鎖在java中是通過(guò)無(wú)鎖編程實(shí)現(xiàn)的,最常見(jiàn)的就是CAS算法,比如Java并發(fā)包中的原子類的遞增操作就是通過(guò)CAS算法實(shí)現(xiàn)的。
CAS算法,其實(shí)就是Compare And Swap(比較與交換)的意思。目的就是將內(nèi)存的值更新為需要的值,但是有個(gè)條件,內(nèi)存值必須與期待的原內(nèi)存值相同。展開(kāi)來(lái)說(shuō),我們有三個(gè)變量,內(nèi)存值M,期望的內(nèi)存值E,更新值U,只有當(dāng)M==E時(shí),才會(huì)將M更新為U。
CAS算法實(shí)現(xiàn)的樂(lè)觀鎖在很多地方有應(yīng)用,比如并發(fā)包的原子類AtomicInteger類。在自增的時(shí)候就使用到CAS算法。
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1); }//var1 是this指針 //var2 是偏移量 //var4 是自增量 public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {//獲取內(nèi)存,稱之為期待的內(nèi)存值Evar5 = this.getIntVolatile(var1, var2);//var5 + var4的結(jié)果是更新值U//這里使用JNI方法,每個(gè)線程將自己內(nèi)存中的內(nèi)存值M與var5期望值比較,//如果相同則更新為var5 + var4,返回true跳出循環(huán)。//如果不相同,則把內(nèi)存值M更新為最新的內(nèi)存值,然后自旋,直到更新成功為止} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//返回更新后的值return var5; }public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);所以可以看出CAS算法其實(shí)是無(wú)鎖的。好處是在讀多寫(xiě)少的情況下,性能是比較好的。那么CAS算法的缺點(diǎn)其實(shí)也是很明顯的。
- ABA問(wèn)題。線程C將內(nèi)存值A(chǔ)改成了B后,又改成了A,而線程D會(huì)認(rèn)為內(nèi)存值A(chǔ)沒(méi)有改變過(guò),這個(gè)問(wèn)題就稱為ABA問(wèn)題。解決辦法很簡(jiǎn)單,在變量前面加上版本號(hào),每次變量更新的時(shí)候變量的版本號(hào)都+1,即A->B->A就變成了1A->2B->3A。
- 在寫(xiě)多讀少的情況下,也就是頻繁更新數(shù)據(jù),那么會(huì)導(dǎo)致其他線程經(jīng)常更新失敗,那么就會(huì)進(jìn)入自旋,自旋時(shí)會(huì)占用CPU資源。如果資源競(jìng)爭(zhēng)激烈,多線程自旋的時(shí)間長(zhǎng),導(dǎo)致消耗資源。
使用場(chǎng)景
在讀多寫(xiě)少的場(chǎng)景下,更新時(shí)很少發(fā)生沖突,使用樂(lè)觀鎖,減少了上鎖和釋放鎖的開(kāi)銷,可以有效地提升系統(tǒng)的性能。
相反,在寫(xiě)多讀少的場(chǎng)景下,如果使用樂(lè)觀鎖會(huì)導(dǎo)致更新時(shí)經(jīng)常產(chǎn)生沖突,然后線程會(huì)循環(huán)重試,這樣會(huì)增大CPU的消耗。在這種情況下,建議可以使用悲觀鎖。
總結(jié)
在日常的開(kāi)發(fā)中,悲觀鎖和樂(lè)觀鎖應(yīng)該是見(jiàn)得最多,用得最多的鎖,比如最常見(jiàn)的synchronized和ReentrantLock是悲觀鎖,并發(fā)包中的原子類和ConcurrentHashMap則用了樂(lè)觀鎖。鎖的實(shí)現(xiàn)并不復(fù)雜,關(guān)鍵是搞懂這兩種鎖的思想,這樣才能在合適的地方使用合適的鎖。
這篇文章就講到這里了,希望看完后能有所收獲,感謝你的閱讀。
覺(jué)得有用就點(diǎn)個(gè)贊吧,你的點(diǎn)贊是我創(chuàng)作的最大動(dòng)力~
我是一個(gè)努力讓大家記住的程序員。我們下期再見(jiàn)!!!
能力有限,如果有什么錯(cuò)誤或者不當(dāng)之處,請(qǐng)大家批評(píng)指正,一起學(xué)習(xí)交流!總結(jié)
以上是生活随笔為你收集整理的乐观锁和悲观锁_什么是悲观锁和乐观锁?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python dict遍历_python
- 下一篇: latex自动生成中文目录_texpad