天天看点

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 
  }
}
           

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

继续阅读