java並發之cas(無鎖,自旋)


java並發之cas(無鎖,自旋)

JDK5之前都是通過synchronized這種悲觀鎖的形式,其它線程競爭時所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖,相當耗資源。

鎖機制存在以下問題:

(1)在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題。

(2)一個線程持有鎖會導致其它所有需要此鎖的線程掛起。

(3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先級倒置,引起性能風險。

后面引入了cas(compare and swap),無鎖,自旋。

什么是無鎖?顧名思義,就是沒有鎖,不需要通過synchronized關鍵字修飾;

什么是自旋?自旋這一概念是在從鎖升級上提出來的,鎖升級步驟如下:

第零級:無鎖狀態

第一級:偏向鎖,第一個線程通過synchronized(Object)在jvm上並沒有加上鎖,只是標記了該object上的線程ID,這個時候為偏向鎖;

(當線程1訪問代碼塊並獲取鎖對象時,會在java對象頭和棧幀中記錄偏向的鎖的threadID,因為偏向鎖不會主動釋放鎖,因此以后線程1再次獲取鎖的時候,需要比較當前線程的threadID和Java對象頭中的threadID是否一致,如果一致(還是線程1獲取鎖對象),則無需使用CAS來加鎖、解鎖;如果不一致(其他線程,如線程2要競爭鎖對象,而偏向鎖不會主動釋放因此還是存儲的線程1的threadID),那么需要查看Java對象頭中記錄的線程1是否存活,如果沒有存活,那么鎖對象被重置為無鎖狀態,其它線程(線程2)可以競爭將其設置為偏向鎖;如果存活,那么立刻查找該線程(線程1)的棧幀信息,如果還是需要繼續持有這個鎖對象,那么暫停當前線程1,撤銷偏向鎖,升級為輕量級鎖,如果線程1 不再使用該鎖對象,那么將鎖對象狀態設為無鎖狀態,重新偏向新的線程。)

第二級:自選鎖(輕量級鎖),線程通過synchronized(Object),當其他線程還沒有是否釋放,那么該線程會自旋等待,類似while(true),默認超過十次就會升級為下一層級;

第三級:自旋次數到了線程1還沒有釋放鎖,或者線程1還在執行,線程2還在自旋等待,這時又有一個線程3過來競爭這個鎖對象,那么這個時候輕量級鎖就會膨脹為重量級鎖。

 

樂觀鎖:CAS,Compare and Swap

cas是一種用於在多線程環境下實現同步功能的機制。CAS 操作包含三個操作數 -- 內存位置、預期數值和新值。CAS 的實現邏輯是將內存位置處的數值與預期數值想比較,若相等,則將內存位置處的值替換為新值。若不相等,則不做任何操作。

case (v,excepted,new)
if(v == excepted){
//傳入值和期望值相等
v=new
}else{
//try again or fail
}

這里引入其他博主的cas遠離做補充

https://blog.csdn.net/u011506543/article/details/82392338

CAS看起很爽,但是會出現ABA問題

ABA問題

CAS算法實現一個重要前提需要取出內存中某時刻的數據,而在下時刻比較並替換,那么在這個時間差類會導致數據的變化。比如說一個線程one從內存位置V中取出A,這時候另一個線程two也從內存中取出A,並且two進行了一些操作變成了B,然后two又將V位置的數據變成A,這時候線程one進行CAS操作發現內存中仍然是A,然后one操作成功。盡管線程one的CAS操作成功,但是不代表這個過程就是沒有問題的。如果鏈表的頭在變化了兩次后恢復了原值,但是不代表鏈表就沒有變化。

解決辦法:  用AtomicStampedReference/AtomicMarkableReference解決ABA問題,實質也是通過stamp來做進一步校驗,類似version樂觀鎖來判斷。

//參數代表的含義分別是 期望值,寫入的新值,期望標記,新標記值
public boolean compareAndSet(V expected,V newReference,int expectedStamp,int newStamp);

public V getRerference();

public int getStamp();

public void set(V newReference,int newStamp);
import java.util.concurrent.atomic.AtomicStampedReference;

public class Test {

    public static void main(String[] args) {
        // https://my.oschina.net/senman/blog/3215704/print
        Counter count = new Counter();
        count.increment();
        count.increment();
        System.out.println(count.getCount());
        count.decrement();
        System.out.println(count.getCount());
    }

}

class Counter {
    private AtomicStampedReference<Integer> count = new AtomicStampedReference<Integer>(0, 0);

    public int getCount() {
        return count.getReference();
    }

    public int increment() {
        int[] stamp = new int[1];
        while (true) {
            Integer value = count.get(stamp); // 同時獲取時間戳和數據,防止獲取到數據和版本不是一致的
            int newValue = value + 1;
            boolean writeOk = count.compareAndSet(value, newValue, stamp[0], stamp[0] + 1);
            if (writeOk) {
                return newValue;
            }
        }
    }

    public int decrement() {
        int[] stamp = new int[1];
        while (true) {
            Integer value = count.get(stamp);// 同時獲取時間戳和數據,防止獲取到數據和版本不是一致的
            int newValue = value - 1;
            boolean writeOk = count.compareAndSet(value, newValue, stamp[0], stamp[0] + 1);
            if (writeOk) {
                return newValue;
            }
        }
    }
}

class Count2 {
    private AtomicStampedReference<Integer> count = new AtomicStampedReference<Integer>(0, 0);

    public int getCount() {
        return count.getReference();
    }

    public int increment() {
        while (true) {
            // 必須先獲取stamp,然后取值,順序不能反,否則仍然會有ABA的問題
            int stamp = count.getStamp();
            Integer value = count.getReference();
            int newValue = value + 1;
            boolean writeOk = count.compareAndSet(value, newValue, stamp, stamp + 1);
            if (writeOk) {
                return newValue;
            }
        }
    }

    public int decrement() {
        while (true) {
            // 必須先獲取stamp,然后取值,順序不能反,否則仍然會有ABA的問題
            int stamp = count.getStamp();
            Integer value = count.getReference();
            int newValue = value - 1;
            boolean writeOk = count.compareAndSet(value, newValue, stamp, stamp + 1);
            if (writeOk) {
                return newValue;
            }
        }
    }
}

也可以通過原子類來聲明變量

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM