天天看點

CAS與Unsafe

CAS

作業系統實作鎖的方式:

          1.對于單處理器來說,防止中斷進行中的并發可簡單采用關閉中斷的方式,即在标志寄存器中關閉/打開中斷标志位。

          2.多處理器,利用test_and_set,Compare_and_swap指令實作程序互斥.

                   是以,CAS是一種系統原語,原語屬于作業系統用語範疇,是由若幹條指令組成的,用于完成某個功能的一個過程,并且原語的執行必須是連續的,在執行過程中不允許被中斷,也就是說CAS是一條CPU的原子指令,不會造成所謂的資料不一緻問題。是以,當多個線程執行CAS操作并且CAS的步驟很多,有沒有可能在判斷V和E相同後,正要指派時,切換了線程,更改了值。造成了資料不一緻的情況是不存在的。

#define LOCKED 1
int TestAndSet(int* lockPtr) {//傳入一位址
     int oldValue;//聲明一變量 
     oldValue = *lockPtr;//把變量設定為 lockPtr位址所指往的值
    *lockPtr = LOCKED;//把原變量設為1
     return oldValue;//傳回舊值
}


volatile int lock = 0;

void Critical() {
     while (TestAndSet(&lock) == 1);
     //to do 我們要寫的業務代碼
     lock = 0 //解放我們的鎖 
}


現線上程1,2調用Critical,因為test_and_set 是指令實作為原子性。
假設如果 線程1先進入,  lock=1并反回 0; 然後執行業務代碼,然後線程2進入,lock=1,傳回1,繼續自旋。直到1将鎖釋放。
Compare_and_swap 則是對test_and_set更新。因為 test_and_set 的 LOCKED 已經定死了。
int compare_and_swap(int* reg, int oldval, int newval)

意思是 oldval和你期望值是否相等,如果相等則設值為newval,并傳回老值。 
           

CAS的缺點:

CAS雖然很高效的解決了原子操作問題,但是CAS仍然存在三大問題。

    1.循環時間長開銷很大。

          如果CAS長時間一直不成功,可能會給CPU帶來很大的開銷。

         (可以試2次CAS,然後挂起線程解決)

    2.隻能保證一個共享變量的原子操作。

         當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖來保證原子性。

        (可以,用一個CAS來擷取共享變量的操作權限,進而達到控制多個變量的效果)

     3.ABA問題。

            如果記憶體位址V初次讀取的值是A,并且在準備指派的時候檢查到它的值仍然為A,那我們就能說它的值沒有被其他線程改變過了嗎?如果在這段期間它的值曾經被改成了B,後來又被改回為A,那CAS操作就會誤認為它從來沒有被改變過。這個漏洞稱為CAS操作的“ABA”問題。

        (Java并發包為了解決這個問題,提供了一個帶有标記的原子引用類“AtomicStampedReference”,它可以通過控制變量值的版本來保證CAS的正确性。是以,在使用CAS前要考慮清楚“ABA”問題是否會影響程式并發的正确性,如果需要解決ABA問題,改用傳統的互斥同步可能會比原子類更高效。)

Unsafe

Unsafe類,由于Java方法無法直接通路底層系統,需要通過本地(native)方法來通路,Unsafe相當于jdk給java開的一個後門,基于該類可以直接操作一個特定記憶體的資料。

1.  通過Unsafe類可以配置設定記憶體,可以釋放記憶體

public native long allocateMemory(long l);
public native long reallocateMemory(long l, long l1);
public native void freeMemory(long l);
           

2. 可以定位對象某字段的記憶體位置,也可以修改對象的字段值,即使它是私有的;

// JAVA中對象的字段的定位可能通過staticFieldOffset方法實作,該方法傳回給定field的記憶體位址偏移量
    public native long staticFieldOffset(Field field);
    // 擷取對象中offset偏移位址對應的整型field的值
    public native int getIntVolatile(Object obj, long l);
    // 擷取對象中offset偏移位址對應的long型field的值
    public native long getLong(Object obj, long l);
    public native int arrayBaseOffset(Class class1);
    public native int arrayIndexScale(Class class1);
           

   對以上方法不太了解的可以參考《Java對象記憶體表示機制》

3. 線程挂起與恢複

将一個線程進行挂起是通過park方法實作的,調用 park後,線程将一直阻塞直到逾時或者中斷等條件出現。unpark可以終止一個挂起的線程,使其恢複正常。整個并發架構中對線程的挂起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法,但最終都調用了Unsafe.park()方法。

// 是對unsafe線程阻塞方法包裝工具類
// 所有的方法都是靜态方法,可以讓線程在任意位置阻塞,當然阻塞之後肯定得有喚醒的方法
public class LockSupport {
    public static void unpark(Thread thread) {
        if (thread != null)
            unsafe.unpark(thread);
    }

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        unsafe.park(false, 0L);
        setBlocker(t, null);
    }

    public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            unsafe.park(false, nanos);
            setBlocker(t, null);
        }
    }

    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        unsafe.park(true, deadline);
        setBlocker(t, null);
    }

    public static void park() {
        unsafe.park(false, 0L);
    }

    public static void parkNanos(long nanos) {
        if (nanos > 0)
            unsafe.park(false, nanos);
    }

    public static void parkUntil(long deadline) {
        unsafe.park(true, deadline);
    }
}
           

4. CAS操作

/**
* 比較obj的offset處記憶體位置中的值和期望的值,如果相同則更新。此更新是不可中斷的。
* 
* @param obj 需要更新的對象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect與field的目前值相同,設定filed的值為這個新值
* @return 如果field的值被更改傳回true
*/
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
           

CAS操作有3個操作數,記憶體值M,預期值E,新值U,如果M==E,則将記憶體值修改為B,否則啥都不做。

主要參考

《Java中Unsafe類詳解》