溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么實(shí)現(xiàn)Java樂觀鎖與悲觀鎖

發(fā)布時(shí)間:2021-11-16 15:28:37 來源:億速云 閱讀:162 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要介紹“怎么實(shí)現(xiàn)Java樂觀鎖與悲觀鎖”,在日常操作中,相信很多人在怎么實(shí)現(xiàn)Java樂觀鎖與悲觀鎖問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對(duì)大家解答”怎么實(shí)現(xiàn)Java樂觀鎖與悲觀鎖”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

悲觀鎖

總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨(dú)占鎖就是悲觀鎖思想的實(shí)現(xiàn)。

樂觀鎖
總是假設(shè)最好的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)機(jī)制和CAS算法實(shí)現(xiàn)。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實(shí)現(xiàn)方式CAS實(shí)現(xiàn)的。

兩種鎖的使用場景
樂觀鎖適用于寫比較少的情況下(多讀場景),即沖突真的很少發(fā)生的時(shí)候,這樣可以省去了鎖的開銷,加大了系統(tǒng)的整個(gè)吞吐量。但如果是多寫的情況,一般會(huì)經(jīng)常產(chǎn)生沖突,這就會(huì)導(dǎo)致上層應(yīng)用會(huì)不斷的進(jìn)行retry,這樣反倒是降低了性能,所以一般多寫的場景下用悲觀鎖就比較合適。

樂觀鎖常見的兩種實(shí)現(xiàn)方式
樂觀鎖一般會(huì)使用版本號(hào)機(jī)制或CAS算法實(shí)現(xiàn)。

1.版本號(hào)機(jī)制

一般是在數(shù)據(jù)表中加上一個(gè)數(shù)據(jù)版本號(hào)version字段,表示數(shù)據(jù)被修改的次數(shù),當(dāng)數(shù)據(jù)被修改時(shí),version值會(huì)加一。當(dāng)線程A要更新數(shù)據(jù)值時(shí),在讀取數(shù)據(jù)的同時(shí)也會(huì)讀取version值,在提交更新時(shí),若剛才讀取到的version值為當(dāng)前數(shù)據(jù)庫中的version值相等時(shí)才更新,否則重試更新操作,直到更新成功。

2.CAS算法
即compare and swap(比較與交換),是一種有名的無鎖算法。無鎖編程,即不使用鎖的情況下實(shí)現(xiàn)多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實(shí)現(xiàn)變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三個(gè)操作數(shù):

需要讀寫的內(nèi)存值 V
進(jìn)行比較的值 A
擬寫入的新值 B
當(dāng)且僅當(dāng) V 的值等于 A時(shí),CAS通過原子方式用新值B來更新V的值,否則不會(huì)執(zhí)行任何操作(比較和替換是一個(gè)原子操作)。一般情況下是一個(gè)自旋操作,即不斷的重試。

樂觀鎖帶來的問題:ABA

線程1準(zhǔn)備用CAS將變量的值由A替換為B,在此之前,線程2將變量的值由A替換為C,又由C替換為A,然后線程1執(zhí)行CAS時(shí)發(fā)現(xiàn)變量的值仍然為A,所以CAS成功。但實(shí)際上這時(shí)的現(xiàn)場已經(jīng)和最初不同了,盡管CAS成功,但可能存在潛藏的問題,例如下面的例子:

現(xiàn)有一個(gè)用單向鏈表實(shí)現(xiàn)的堆棧,棧頂為A,這時(shí)線程T1已經(jīng)知道A.next為B,然后希望用CAS將棧頂替換為B;

在T1執(zhí)行上面這條指令之前,線程T2介入,將A、B出棧,再pushD、C、A,此時(shí)堆棧結(jié)構(gòu)如下圖,而對(duì)象B此時(shí)處于游離狀態(tài);

此時(shí)輪到線程T1執(zhí)行CAS操作,檢測發(fā)現(xiàn)棧頂仍為A,所以CAS成功,棧頂變?yōu)锽,但實(shí)際上B.next為null,所以此時(shí)的情況變?yōu)椋?/p>

其中堆棧中只有B一個(gè)元素,C和D組成的鏈表不再存在于堆棧中,平白無故就把C、D丟掉了。

解決方法

各種樂觀鎖的實(shí)現(xiàn)中通常都會(huì)用版本戳version來對(duì)記錄或?qū)ο髽?biāo)記,避免并發(fā)操作帶來的問題,在Java中,AtomicStampedReference<E>也實(shí)現(xiàn)了這個(gè)作用,它通過包裝[E,Integer]的元組來對(duì)對(duì)象標(biāo)記版本戳stamp,從而避免ABA問題,其中compareAndSet方法會(huì)首先檢查當(dāng)前引用是否等于預(yù)期引用,其次檢查當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志,如果都相等就會(huì)以原子的方式將引用和標(biāo)志都設(shè)置為新值。

例如下面的代碼分別用AtomicInteger和AtomicStampedReference來對(duì)初始值為100的原子整型變量進(jìn)行更新,AtomicInteger會(huì)成功執(zhí)行CAS操作,而加上版本戳的AtomicStampedReference對(duì)于ABA問題會(huì)執(zhí)行CAS失?。?/p>

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABA {
        private static AtomicInteger atomicInt = new AtomicInteger(100);
        private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0);

        public static void main(String[] args) throws InterruptedException {
                Thread intT1 = new Thread(new Runnable() {
                        @Override
                        public void run() {
                                atomicInt.compareAndSet(100, 101);
                                atomicInt.compareAndSet(101, 100);
                        }
                });

                Thread intT2 = new Thread(new Runnable() {
                        @Override
                        public void run() {
                                try {
                                        TimeUnit.SECONDS.sleep(1);
                                } catch (InterruptedException e) {
                                }
                                boolean c3 = atomicInt.compareAndSet(100, 101);
                                System.out.println(c3); // true
                        }
                });

                intT1.start();
                intT2.start();
                intT1.join();
                intT2.join();

                Thread refT1 = new Thread(new Runnable() {
                        @Override
                        public void run() {
                                try {
                                        TimeUnit.SECONDS.sleep(1);
                                } catch (InterruptedException e) {
                                }
                                atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
                                atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
                        }
                });

                Thread refT2 = new Thread(new Runnable() {
                        @Override
                        public void run() {
                                int stamp = atomicStampedRef.getStamp();
                                try {
                                        TimeUnit.SECONDS.sleep(2);
                                } catch (InterruptedException e) {
                                }
                                boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
                                System.out.println(c3); // false
                        }
                });

                refT1.start();
                refT2.start();
        }
}

到此,關(guān)于“怎么實(shí)現(xiàn)Java樂觀鎖與悲觀鎖”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI