JMM,volatile,AtomicXXX, CAS,Unsafe类,自旋

JMM:本身并不真实存在, 只是一种抽象概念。关于同步的规定:

  1. 线程加锁前,必须读取主内存最新值到自己的工作内存;
  2. 线程解锁前,必须把共享变量刷回主内存
  3. 加锁解锁,同一把锁。

JMM要求三种性质: 可见性,原子性,有序性。

volatile保证了可见性和有序性, 但是不能保证原子性: e.g. i++;

AtomicInteger类创建出来的对象atomicInteger的方法GetAndIncrement()可以满足原子性地执行i++; 因为底层用了CAS的原理,实际上是通过Unsafe类,和自旋操作完成的。

为什么AtomicInteger底层用的CAS而不是synchronized? 因为自旋操作,在保证一致性的同时,提高了并发性。

AtomicInteger.java:

public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

点进getAndAddInt:

/*
    var1: addr <- new AtomicInteger()   var2: valueOffset
    var5: value;  var4: value to add  (1 here)
*/
    int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

可以看到是先得到var5, 再compareAndSwapInt; compareAndSwapInt会一直比较工作内存和主内存中那个需要修改的值,只有expected==value的时候才会修改成功,否则会一直自旋重新拿主物理内存中的值,直到expected==value的时候才会修改成功.

能保证原子性,是因为1. Unsafe.class类;2. 自旋的写法;这一类修改值方法底层原理就是CAS思想.


更细致: 具体为什么getIntVolatile(var1, var2)compareAndSwapInt(var1, var2, var5, var5 + var4)就可以? 这两个方法本身是怎么写的?

public native int getIntVolatile(Object var1, long var2);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

都是native方法。

CAS全称是Compare-And-Swap;是一条CPU并发原语, 体现在Java中sun.mics.Unsafe类中的各个native方法,属于操作系统用语,由若干指令组成, 并且原语的执行必须是连续的,不被中断, i.e. CAS是一条CPU原子指令,不会造成数据不一致问题。 调用Unsafe类中的CAS方法,JVM帮我们实现CAS汇编指令: e.g. 优于加锁,并保证原子性

Unsafe API: 大多数API都是通过native函数实现的,具体可以查看源码中的文件path/to/openjdk/source/hotspot/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

i.e. 拿到value在内存中的地址,通过Atomic::cmpxchg实现比较替换,其中参数x是即将更新的值,参数e是原内存的值。

就是靠的Atomic::cmpxchg底层汇编保证原子性

简单来说,CAS就是比较当前线程工作内存中的值和主内存中值,相同就执行操作,否则继续比较直到主内存和工作内存中的值一致。


synchronized: 保证一致性,并发性下降;
CAS: 不加锁,保证一致性和并发性(do...while自旋代码实现的一致性), 但是可能多次比较

CAS缺点:

  1. 可能长时间在循环♻️中自旋, CPU开销大;
  2. 只能保证一个共享变量的原子性;
  3. ABA问题
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 文章讲解流程JMM ==> volatile 解决可见性 ==>AtomicInteger解决原子性 ==> CA...
    Minority阅读 411评论 0 1
  • 在Java并发中,我们最初接触的应该就是synchronized关键字了,但是synchronized属于重量级锁...
    tracy_668阅读 1,894评论 2 23
  • java.util.concurrent包完全建立在CAS之上。 AQS,非阻塞数据结构和原子变量类(java.u...
    光剑书架上的书阅读 5,690评论 0 8
  • 被压在记忆底端 平常的日子 没有人会想起流逝的故事 只是把明天念了又念 尘封已久的童趣 掠过远去的飞鸿 沉寂的满天...
    我是一片云_d288阅读 168评论 0 4
  • 前言:苹果的官方文档《Event Handling Guide for iOS》对事件处理做了非常详尽清晰的解释,...
    牛小牛啊阅读 366评论 0 2