中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java中如何實現悲觀鎖與樂觀鎖

發布時間:2021-06-30 17:54:16 來源:億速云 閱讀:1215 作者:Leah 欄目:編程語言

今天就跟大家聊聊有關java中如何實現悲觀鎖與樂觀鎖,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。

1、 悲觀鎖:總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。在Java語言中synchronized關鍵字的實現就悲觀鎖。

2、樂觀鎖:顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS( Conmpare And Swap 比較并交換)實現的。

從上面的描述我們可以看出2種鎖其實各有優劣,不可認為一種好于另一種,像樂觀鎖適用于寫比較少的情況下(多讀場景),即沖突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果是多寫的情況,一般會經常產生沖突,這就會導致上層應用會不斷的進行retry,這樣反倒是降低了性能,所以一般多寫的場景下用悲觀鎖就比較合適。

    悲觀鎖其實沒什么好講,這里主要講解寫樂觀鎖。

    JAVA的樂觀鎖主要采用CAS算法即 compare and swap(比較與交換),是一種有名的無鎖算法。無鎖編程,即不使用鎖的情況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三個操作數

  • 需要讀寫的內存值 V

  • 進行比較的值 A

  • 擬寫入的新值 B

當且僅當 V 的值等于 A時,CAS通過原子方式用新值B來更新V的值,否則不會執行任何操作(比較和替換是一個原子操作)。一般情況下是一個自旋操作,即不斷的重試。正因為不斷重試所以如果長時間不成功,會給CPU帶來非常大的執行開銷。

這樣說或許有些抽象,我們來看一個例子:

1.在內存地址V當中,存儲著值為10的變量。

java中如何實現悲觀鎖與樂觀鎖

2.此時線程1想要把變量的值增加1。對線程1來說,舊的預期值A=10,要修改的新值B=11。

java中如何實現悲觀鎖與樂觀鎖

3.在線程1要提交更新之前,另一個線程2搶先一步,把內存地址V中的變量值率先更新成了11。

java中如何實現悲觀鎖與樂觀鎖

4.線程1開始提交更新,首先進行A和地址V的實際值比較(Compare),發現A不等于V的實際值,提交失敗。

java中如何實現悲觀鎖與樂觀鎖

5.線程1重新獲取內存地址V的當前值,并重新計算想要修改的新值。此時對線程1來說,A=11,B=12。這個重新嘗試的過程被稱為自旋。

java中如何實現悲觀鎖與樂觀鎖

6.這一次比較幸運,沒有其他線程改變地址V的值。線程1進行Compare,發現A和地址V的實際值是相等的。

java中如何實現悲觀鎖與樂觀鎖

7.線程1進行SWAP,把地址V的值替換為B,也就是12。

java中如何實現悲觀鎖與樂觀鎖

在JAVA中是通過Unsafe類提供的一系列compareAndSawp*方法來實現

首先我們先研究下Unsafe,初始化Unsafe用到Unsafe.getUnsafe() ;

通過查看源碼我們發現這個類我們不能直接使用

@CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        //判斷調用類是否BootstrapClassLoader加載,Unsafe是系統Jar包按JAVA雙親委派模式,這個類是由BootstrapClassLoader加載的。而普通項目的類是由CustomClassLoader加載
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

我們繼續看源碼發現有3個CAS操作方法

var1: 要修改的對象 

var2: 對象中field的偏移量 

var4: 期望值(預期的原值) 

var5: 更新值 

返回值 true/false

這里可以看到這幾個方法都是native方法,低層都是調操作系統的方法,這里不深入研究

    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

因為Unsafe不方便調用(當然我們可以通過反射勉強也可以用),所以我們只能拿AtomicInteger來研究下。我們new AtomicInteger()時,會獲取AtomicInteger對像Value字段的偏移量

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
           // 通過Unsafe方法獲取value字段的偏移量(可以理解為C++的指針)
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    private volatile int value;
    ...
}

我們再來看下atomicInteger.getAndIncrement()這個方法的實現

/**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

它最終調用的是unsafe的getAndAddInt方法,我們繼續往下跟蹤

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

由上圖可知,var1是AtomicInteger對象,var2是AtomicInteger對象value字段的偏移量,var5是期望值expected,var5+var4=var5+1。這么說可能比較清楚點,例如:

AtomicInteger atomicInteger = new AtomicInteger(2);
        int a = atomicInteger.getAndIncrement() ;

則var5為2,var5+var4=2+1=3。我們從上上圖可以看出,這里用了個循環也就是說當期望值不是2時會一直循環嘗試。相等就把x值賦值給offset位置的值,不相等,就取消賦值,方法返回false。這也是CAS的思想,及比較并交換。用于保證并發時的無鎖并發的安全性。

這里有同學可能會擔心死循環問題,其實不會的大家可以看下AtomicInteger那個類是設置成volatile類型,也就是內存可見所有線程獲取到的值都是最新的。但是用循環卻會產生ABA的問題。 即如果在此之間,V被修改了兩次,但是最終值還是修改成了舊值V,這個時候,就不好判斷這個共享變量是否已經被修改過。

java中如何實現悲觀鎖與樂觀鎖

為了防止這種不當寫入導致的不確定問題,原子操作類提供了一個帶有時間戳的原子操作類。帶有時間戳的原子操作類AtomicStampedReference  CAS(V,E,N)當帶有時間戳的原子操作類AtomicStampedReference對應的數值被修改時,除了更新數據本身外,還必須要更新時間戳。當AtomicStampedReference設置對象值時,對象值以及時間戳都必須滿足期望值,寫入才會成功。因此,即使對象值被反復讀寫,寫回原值,只要時間戳發生變化,就能防止不恰當的寫入。

以下是AtomicStampedReference類的compareAndSet方法

/**
* Params:
expectedReference – 當前值
newReference – 修改后的值
expectedStamp – 當前時間戳
newStamp – 修改后的時間戳
return true/false
*
*/ 
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp)

以下是我的測試例子

import java.util.concurrent.atomic.AtomicStampedReference;

public class CASTest {
    public static void main(String[] args) {
        int initialStamp = 1;

        AtomicStampedReference<String> atomicStringReference = new AtomicStampedReference<String>( "value1", initialStamp);


        boolean exchanged1 = atomicStringReference.compareAndSet("value1", "value2", initialStamp, initialStamp+1);
        System.out.println("exchanged: ">

看完上述內容,你們對java中如何實現悲觀鎖與樂觀鎖有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

阿城市| 宜州市| 丰台区| 宜城市| 长岭县| 平顶山市| 绥棱县| 珲春市| 武夷山市| 上饶县| 收藏| 田林县| 霸州市| 托克逊县| 普定县| 常熟市| 萨迦县| 方山县| 夹江县| 崇礼县| 灌阳县| 桃园县| 嵊州市| 理塘县| 景洪市| 积石山| 紫金县| 文昌市| 玉门市| 西宁市| 榆中县| 鄂温| 广汉市| 丹棱县| 江西省| 瓦房店市| 玉屏| 沐川县| 平遥县| 巫溪县| 依安县|