天天看點

CAS原子操作底層原理搶紅包的問題compareAndSet

CAS原子操作史上最底層原理

  • 搶紅包的問題
  • compareAndSet
    • objectFieldOffset
    • compareAndSetInt

搶紅包的問題

本篇我隻想講這一個方法,因為其他的CAS操作類似,隻要把這個搞懂了,其他的就不是問題。舉個最簡單的例子,1000個線程要去修改一個值,但是這個值隻能被修改一次,比如1000個人搶1個紅包,但是紅包就隻有一個:

public class NoAtomicTest {
    private static int money = 1;//紅包
    public static void main(String[] args) {
        Thread[] persons = new Thread[1000];
        for (int i = 0; i < 1000; i++) {

            persons[i] = new Thread(() -> {

                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (money == 1) {
                    money = 0;
                    System.out.println(Thread.currentThread().getName() + "搶到紅包");
                }

            },"會員"+i);
        }
        for (int i = 0; i < persons.length; i++) {
            persons[i].start();
        }
    }
}
           

輸出可能是:

會員0搶到紅包
會員5搶到紅包
會員1搶到紅包
           

可以看到一個紅包居然可以三個人搶到,就有問題啦,現在我們用原子操作試試:

public class AtomicTest {

    private static AtomicInteger money = new AtomicInteger(1);

    public static void main(String[] args) {
        Thread[] persons = new Thread[1000];
        for (int i = 0; i < 1000; i++) {

            persons[i] = new Thread(() -> {

                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (money.compareAndSet(1, 0)) {
                    System.out.println(Thread.currentThread().getName() + "搶到紅包");
                }

            }, "會員" + i);
        }
        for (int i = 0; i < persons.length; i++) {
            persons[i].start();
        }
    }
}

           

結果永遠都是1個人搶到。

compareAndSet

例子舉完了,我們得知道為什麼用這個方法可以避免問題,我們要看看這個原子類

AtomicInteger

的一些源碼:

public final boolean compareAndSet(int expectedValue, int newValue) {
        return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
    }
           

先不管參數,會發現是調用

U

compareAndSetInt

,我們看看

U

是什麼:

private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
 public static Unsafe getUnsafe() {
        return theUnsafe;
    }
 private static final Unsafe theUnsafe = new Unsafe();
           

原來是内部的

Unsafe

對象,這個方法簡單來說就是可以操作底層硬體的,可以直接用C/C++語言,可以使用彙編語言的哦。

CAS原子操作底層原理搶紅包的問題compareAndSet

objectFieldOffset

然後我們發現還有個

VALUE

哪裡來的:

private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
public long objectFieldOffset(Class<?> c, String name) {
        if (c == null || name == null) {
            throw new NullPointerException();
        }

        return objectFieldOffset1(c, name);
    }
private native long objectFieldOffset1(Class<?> c, String name);
           

原來是

Unsafe

的方法獲得的,看方法一是就是說獲得對象某個屬性的偏移,也就是

value

屬性的偏移。其實就是記憶體中這個屬性的位址啦,你可以把記憶體位址了解成一個數組,

value

屬性就在數組裡,你要擷取是不是得有索引啊,索引就是相當于偏移,當然實體記憶體中不是那麼簡單存儲。其實最後是調用了本地方法,我們來看看這個的方法到底是在什麼地方,我下了

JDK11

的源碼:

CAS原子操作底層原理搶紅包的問題compareAndSet

是這句:

CAS原子操作底層原理搶紅包的問題compareAndSet

其實就是個宏定義,指向方法:

CAS原子操作底層原理搶紅包的問題compareAndSet

内部又調用了:

CAS原子操作底層原理搶紅包的問題compareAndSet

一堆C++的代碼,其實我也太懂,我猜是調用了

JavaFieldStream

offset

方法來獲得偏移量,這個是

JavaFieldStream

父類

FieldStreamBase

的方法,而且還有其他的方法:

CAS原子操作底層原理搶紅包的問題compareAndSet

内部是調用了

FieldInfo

的方法:

CAS原子操作底層原理搶紅包的問題compareAndSet
CAS原子操作底層原理搶紅包的問題compareAndSet

核心的應該是這句,其實就是把兩個

unsigned short

拼成一個

int

,然後右移

2

位:

16位低位和16位高位(

low_packed_offset

high_packed_offset

)合起來成一個32位:

inline int build_int_from_shorts( jushort low, jushort high ) {
  return ((int)((unsigned int)high << 16) | (unsigned int)low);
}
           

low_packed_offset

high_packed_offset

這兩個參數哪裡來的呢,應該是初始化的時候設定好的:

CAS原子操作底層原理搶紅包的問題compareAndSet

其他的我就不多說啦,你隻要知道這個偏移量是可以從底層的屬性的偏移量中擷取的。

compareAndSetInt

最後還是本地方法:

@HotSpotIntrinsicCandidate
    public final native boolean compareAndSetInt(Object o, long offset,
                                                 int expected,
                                                 int x);
           

這個的意思就是說針對某個對象

o

,根據偏移量

offset

找到屬性值,跟我們所期望的值

expected

是否一樣,如果一樣,就把這個屬性值設定成x,傳回true,否則false。

那我們看看本地方法吧:

CAS原子操作底層原理搶紅包的問題compareAndSet

同樣調用的是

Unsafe_CompareAndSetInt

CAS原子操作底層原理搶紅包的問題compareAndSet

對應的就是

access.hpp

的:

CAS原子操作底層原理搶紅包的問題compareAndSet
CAS原子操作底層原理搶紅包的問題compareAndSet

具體的實作應該就是比如windows上的

atomic_windows_x86.hpp

Atomic::PlatformCmpxchg

方法:

template<>
template<typename T>
inline T Atomic::PlatformCmpxchg<4>::operator()(T exchange_value,
                                                T volatile* dest,
                                                T compare_value,
                                                atomic_memory_order order) const {
  STATIC_ASSERT(4 == sizeof(T));
  // alternative for InterlockedCompareExchange
  __asm {
    mov edx, dest //把屬性位址放進寄存器edx
    mov ecx, exchange_value //把新的屬性值放進寄存器ecx
    mov eax, compare_value //屬性的的期望值
    //取寄存器edx中的值所對應的記憶體位址中的值和eax中的值作比較,如果相等就設定成ecx寄存器中的值。而且還用lock上鎖了,要麼就是總線鎖,要麼就是緩存鎖.
    //[edx]屬于間接尋址,就是擷取到寄存器edx中的值(記憶體位址)後再去記憶體中取值
    //dword ptr 雙字的指針指向記憶體位址
    lock cmpxchg dword ptr [edx], ecx 
  }
}
           

好了,今天就到這裡了,希望對學習了解有幫助,大神看見勿噴,僅為自己的學習了解,能力有限,請多包涵。

繼續閱讀