原子类如何保证操作的原子性?

一、什么是原子性?
所谓原子操作,就是"不可中断的一个或一系列操作" 。

硬件级的原子操作:
在单处理器系统中,能够在单条指令中完成的操作都可以认为是" 原子操作",因为中断只能发生于指令之间。即硬件上已经保证了单CPU上单条指令的原子性。

在对称多处理器结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。

二、为什么普通的i++不是原子性操作?
1.i++分为三个阶段:

1、内存到寄存器(高速缓冲区L1,L2,L3)
2、寄存器自增
3、写回内存

这三个阶段中间都可以被中断分离开.

2.i++首先要看编译器是怎么编译的:

    __asm
    {
        movl x, %eax
        addl $1, %eax
        movl %eax, x
    }

这种情况下,必定不是原子操作,因其步骤包括了从内存中取x值放入寄存器,加寄存器,把值写入内存三个指令。不加锁互斥是不行的。

三、为什么原子类能保证原子性?
1、追寻源码:

    public final int getAndSet(int newValue) {
            for (;; ) {
                int current = get();
                if (compareAndSet(current, newValue))
                    return current;
            }
        }

    public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }

    public final native boolean compareAndSwapInt(Object o, long offset, int expected,int x);

查看本地方法compareAndSwapInt底层实现代码,位置在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

这个类的实现是跟操作系统有关, 跟CPU架构也有关, 如果是windows下x86的架构实现在hotspot\src\os_cpu\windows_x86\vm\atomic_windows_x86.inline.hpp文件里

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

如果是Linux的x86,路径为hotspot\src\os_cpu\linux_x86\vm\atomic_linux_x86.inline.hpp
__asm__表示汇编的开始 volatile表示禁止编译器优化 LOCK_IF_MP是个内联函数:

    #define LOCK_IF_MP(mp) __asm cmp mp, 0  \
                           __asm je L0      \
                           __asm _emit 0xF0 \
                           __asm L0:

LOCK_IF_MP根据当前系统是否为多核处理器决定是否为cmpxchg指令添加lock前缀。
1、如果是多处理器,为cmpxchg指令添加lock前缀。
2、反之,就省略lock前缀。(单处理器会不需要lock前缀提供的内存屏障效果)

intel手册对lock前缀的说明如下:
1、确保后续指令执行的原子性。在Pentium及之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其它处理器暂时无法通过总线访问内存,很显然,这个开销很大。在新的处理器中,Intel使用缓存锁定来保证指令执行的原子性,缓存锁定将大大降低lock前缀指令的执行开销。
2、禁止该指令与前面和后面的读写指令重排序。
3、把写缓冲区的所有数据刷新到内存中。
上面的第2点和第3点所具有的内存屏障效果,保证了CAS同时具有volatile读和volatile写的内存语义。

    cmpxchg:
    	if(accumulator == Destination) {
    		ZF = 1;
    		Destination = Source;
    	}else {
    		ZF = 0;
    		accumulator = Destination;
    	}

目标值和寄存器里的值相等的话,就设置一个跳转标志,并且把原始数据设到目标里面去。如果不等的话,就不设置跳转标志了。
在这里可以看到是用嵌入的汇编实现的, 关键CPU指令是 cmpxchg。
也就是说CAS的原子性实际上是CPU实现的. 其实在这一点上还是有排他锁的. 只是比起用synchronized, 这里的排他时间要短的多. 所以在多线程情况下性能会比较好。

如上面源代码所示,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值