并发编程-Atomic的compareAndSet

接上一篇博客并发编程-多线程共享变量不安全,分析Atomic原子类是怎么保证线程安全的。

并发三个基本概念:

1.原子性:操作是线程私有的,不能拆分成多个步骤,被其他线程影响;(官方版:操作不可中断,全部执行或全部失败)

2.可见性:对共享变量的修改能够被其他线程可见;

3.有序性:cpu会为了优化对指令进行重排序,有序性保证线程内指令顺序一致。

看AtomicInteger的incrementAndGet方法如何保证原子性的。以下代码是CAS的过程:

public final int incrementAndGet() {
        for (;;) {
            int current = get(); //从主内存中拷贝value放到本地线程栈内存中 这二行代码可能另一个线程也在执行
            int next = current + 1; //做增加操作
            if (compareAndSet(current, next)) 
                return next;
        }
    }
//调用unsafe的compareAndSwapInt方法
public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

public final int get() {
        return value;
    }

private volatile int value;  //volatile保证了可见性,在incrementAndGet()方法中get()方法获取的value是从主内存中拿到的
private static final Unsafe unsafe = Unsafe.getUnsafe(); 

private static final long valueOffset;
static {
try {
valueOffset
= unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); //获取value值在AtomicInteger内存地址中的偏移量
        }
catch (Exception ex)
{ throw new Error(ex); }
}

//Unsafe class 代码 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

原理就是:线程A从主存中拿到value值,对value值做+1操作赋给newValue,然后再获取一次主内存的值,比较value和原值是否相等,如果相等,证明这个值没有被别的线程改变,对它设置新的值,如果不相等,证明这个值被别的线程更改了,重新获取一次,再次比较,直到相等、设置新值,返回新值。

再来看一下compareAndSwapInt方法 unsafe.cpp,(http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/share/vm/prims/unsafe.cpp):

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

主要实现Atomic::cmpxchg(x, addr, e)  继续看Atomic::cmpxchg , 这个本地方法的最终实现在openjdk的如下位置:

openjdk-7-fcs-src-b147-27jun2011openjdkhotspotsrcoscpuwindowsx86vmatomicwindowsx86.inline.hpp
(对应于windows操作系统,X86处理器)
// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0  
                       __asm je L0      
                       __asm _emit 0xF0 
                       __asm L0:
 
inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();  //判断当前系统环境
  __asm {
    mov edx, dest   
    mov ecx, exchange_value
    mov eax, compare_value
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}
LOCK_IF_MP指的是如果是在多核环境时,cpu会加上lock,如果是单核环境,不会加lock,因此实际上也是通过lock在cpu层面加上了屏障,排他锁。但是cpu层面的锁比jvm层的sync锁
效率要高很多。
 
欢迎关注Java流水账公众号
原文地址:https://www.cnblogs.com/guofu-angela/p/9363654.html