java的樂觀鎖和悲觀鎖

參考:

https://www.cnblogs.com/jyroy/p/11365935.html

https://www.jianshu.com/p/ae25eb3cfb5d

樂觀鎖和悲觀鎖

樂觀鎖和悲觀鎖是一種廣義上的概念,體現了看待線程同步的不同角度。

樂觀鎖:對於併發操作產生的線程安全問題持樂觀態度,認爲自己在使用數據時不會有別的線程修改數據,所以不會添加鎖,只是在更新數據的時候去判斷之前有沒有別的線程更新了這個數據,如果這個數據沒有被更新,當前線程將自己修改的數據成功寫入。如果數據已經被其他線程更新,則執行其他操作,如報錯或自動重試。樂觀鎖最常用的是CAS算法,java java.util.concurrent包下的原子類(例如AtomicBooleanAtomicIntegerAtomicLong)中的遞增操作通過CAS自旋來實現。

悲觀鎖:它是對於併發操作時產生的線程安全持悲觀心態,悲觀鎖認爲競爭總會發生,因此每次對於某些進行操作時,都會持有一個獨佔的鎖,像synchronied和lock的實現類都是悲觀鎖。

 

適用場景:
悲觀鎖適用於寫操作多的場景,先加鎖可以保證寫操作時數據正確。
樂觀鎖適合讀操作多的場景,不加鎖可以使讀操作的性能大幅提升。

什麼是CAS?


CAS:比較與交換 ,是一種無鎖算法。在不適用鎖(沒有線程阻塞)的情況下實現多線程之間的變量同步。java.util.concurrent包的原子類就是通過cas來實現樂觀鎖。

CAS算法涉及到三個操作數:內存地址V,舊的預期值A,要修改的新值B。
更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,纔會將內存地址V對應的值修改爲B。

看個例子:

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

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

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

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

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

6、這一次比較幸運,沒有其他線程改變地址V的值。線程1進行Compare,發現A和地址V的實際值是相等的。線程1可以進行SWAP,把地址V的值替換爲B,也就是12。

 

前面我們說到java java.util.concurrent包下的原子類的自增操作是利用了CAS思想。我們查看AtomicInteger的自增函數incrementAndGet(),發現自增函數底層調用的是unsafe.getAndAddInt()。進一步查看unsafe.getAndAddInt() 的實現。

從上圖中的源碼中,我們可以看到getAndAddInt()循環獲取給定對象O中的偏移量處的值v,然後判斷內存值是否等於v, 如果相等則將內存值設置爲v+delta,否則返回false,繼續循環進行充實,知道設置成功才能推出循環,並且將舊值返回。整個“比較和更新”操作封裝在compareAndSwapInt(),在JNI裏藉助於一個CPU指令完成的,屬於原子操作,可以保證多個線程都能夠看到同一個變量的修改值。
 

CAS的缺點:

1.CPU開銷較大
在併發量比較高的情況下,如果許多線程反覆嘗試更新某一個變量,卻又一直更新不成功,循環往復,會給CPU帶來很大的壓力。

2.不能保證多個的原子性。
CAS機制所保證的只是一個變量的原子性操作,而不能保證整個代碼塊的原子性。比如需要保證3個變量共同進行原子性的更新,就不得不使用獨佔鎖了。

3、 ABA問題。CAS需要在操作值的時候檢查內存值是否發生變化,沒有發生變化纔會更新內存值。但是如果內存值原來是A,後來變成了B,然後又變成了A,那麼CAS進行檢查時會發現值沒有發生變化,但是實際上是有變化的。ABA問題的解決思路就是在變量前面添加版本號,每次變量更新的時候都把版本號加一,這樣變化過程就從“A-B-A”變成了“1A-2B-3A”。

JDK從1.5開始提供了AtomicStampedReference類來解決ABA問題,具體操作封裝在compareAndSet()中。compareAndSet()首先檢查當前引用和當前標誌與預期引用和預期標誌是否相等,如果都相等,則以原子方式將引用值和標誌的值設置爲給定的更新值。

一個使用AtomicInteger的例子:

import java.util.concurrent.atomic.AtomicInteger;
class Untitled {

    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    //每個線程讓count自增100次
                    for (int i = 0; i < 100; i++) {
                        count.incrementAndGet();
                    }
                }
            }).start();
        }

        try{
            Thread.sleep(2000);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(count);
    }
}

換成Synchronized:

class Untitled {
	private static int count = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    //每個線程讓count自增100次
                    for (int i = 0; i < 100; i++) {
                        synchronized (Untitled.class){
                            count++;
                        }
                    }
                }
            }).start();
        }

        try{
            Thread.sleep(2000);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(count);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章