CAS底层

CAS(Compare And Swap 比较并且替换)是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的。

线程在读取数据时不进行加锁,在准备写回数据时,先去查询原值,操作的时候比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。

CAS并发原语提现在Java语言中就是sun.miscUnSafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我实现CAS汇编指令.这是一种完全依赖于硬件 功能,通过它实现了原子操作。

AtomicInteger atomicInteger = new AtomicInteger(0);
atomicInteger.compareAndSet(0,1);
System.out.println(atomicInteger);
​
output:1
​
AtomicInteger源码:
// 初始化赋值
// 被volatile修饰,保证了多线程之间的可见性
private volatile int value;
public AtomicInteger(int initialValue) {
        value = initialValue;
    }
​
private static final Unsafe U = Unsafe.getUnsafe();
// 它是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的
private static final long VALUE
        = U.objectFieldOffset(AtomicInteger.class, "value");
/** @return true if successful. False return indicates that the actual value was not equal to the expected value. */
public final boolean compareAndSet(int expectedValue, int newValue) {
        return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
    }
​
/** UnSafe类是CAS的核心类由于Java 方法无法直接访问底层,需要通过本地(native)方法来访问,基于该类可以直接操作特额定的内存数据.UnSafe类在于sun.misc包中,其内部方法操作可以向C的指针一样直接操作内存,因为Java中CAS操作的依赖于UNSafe类的方法。*/
Unsafe源码:
/** @return true if successful */
@IntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
                                                 int expected,
                                                 int x);
hotspot源码:hotspot 是C++写的
jdk8u: 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);
  //cmpxchg = compare and exchange
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
​
jdk8u: atomic_linux_x86.inline.hpp 源码
​
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint     compare_value) {
  //is_MP = Multi Processor  ,
  int mp = os::is_MP();
  //assembly汇编语言
  //MP(multi processors)多处理器
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}

可以看到CAS操作在底层有一条对应的汇编指令,硬件直接支持。LOCK_IF_MP,如果是多个CPU,前面还要加一条LOCK指令,本身cmpxchg指令是没有原子性的,在多个cpu执行时,加上LOCK指令就是保证一个CPU执行后面的empxchg指令时,其他CPU不能执行。在硬件层,LOCK指令在执行后面指令的时候锁定一个北桥信号(不采用锁总线方式)。所以CAS最终实现就是lock cmpxchg指令

1.1、ABA问题

1、线程1读取了数据A

2、线程2读取了数据A

3、线程2通过CAS比较,发现值是A没错,可以把数据A改成数据B

4、线程3读取了数据B

5、线程3通过CAS比较,发现数据是B没错,可以把数据B改成了数据A

6、线程1通过CAS比较,发现数据还是A没变,就写成了自己要改的值

任何线程都没做错什么,但是值被改变了,线程1却没有办法发现,其实这样的情况出现对结果本身是没有什么影响的,但是我们还是要防范。

解决方法:

1、加标志位,例如搞个自增的字段,操作一次就自增加一,或者搞个时间戳,比较时间戳的值。

2、从Java1.5开始JDK的atomic包里提供了一个类 AtomicStampedReference 来解决ABA问题。这个类的 compareAndSet 方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

1.2、循环时间长开销大问题

CAS操作长时间不成功的话,会导致一直自旋,相当于死循环了,CPU的压力会很大。

解决方法:

1、如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

1.3、只能保证一个共享变量的原子操作

CAS操作单个共享变量的时候可以保证原子的操作,多个变量就不行了。

解决方法:

1、JDK 5之后 AtomicReference可以用来保证对象之间的原子性,就可以把多个对象放入CAS中操作。

你可能感兴趣的:(多线程,jvm,java,开发语言)