zoukankan      html  css  js  c++  java
  • 浅析CompareAndSet(CAS)

    最近无意接触了AtomicInteger类compareAndSet(从JDK5开始),搜了搜相关资料,整理了一下

    首先要说一下,AtomicInteger类compareAndSet通过原子操作实现了CAS操作,最底层基于汇编语言实现。

    简单说一下原子操作的概念,“原子”代表最小的单位,所以原子操作可以看做最小的执行单位,该操作在执行完毕前不会被任何其他任务或事件打断。

    CAS是Compare And Set的一个简称,如下理解:

    1,已知当前内存里面的值current和预期要修改成的值new传入

    2,内存中AtomicInteger对象地址对应的真实值(因为有可能别修改)real与current对比,

          相等表示real未被修改过,是“安全”的,将new赋给real结束然后返回;不相等说明real已经被修改,结束并重新执行1直到修改成功

    CAS相比Synchronized,避免了锁的使用,总体性能比Synchronized高很多.

    compareAndSet典型使用为计数,如i++,++i,这里以i++为例:

        /**
         * Atomically increments by one the current value.
         *
         * @return the updated value
         */
        public final int incrementAndGet() {
            for (;;) {
                //获取当前值
                int current = get();
                //设置期望值
                int next = current + 1;
                //调用Native方法compareAndSet,执行CAS操作
                if (compareAndSet(current, next))
                    //成功后才会返回期望值,否则无线循环
                    return next;
            }
        }


    compareAndSet方法实现:

    JDK文档对该方法的说明如下:如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值。

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

      


    这里解释一下valueOffset变量,首先valueOffset的初始化在static静态代码块里面,代表相对起始内存地址的字节相对偏移量:

    private static final long valueOffset;
        static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    

      


    在生成一个AtomicInteger对象后,可以看做生成了一段内存,对象中各个字段按一定顺序放在这段内存中,字段可能不是连续放置的,

    unsafe.objectFieldOffset(Field f)这个方法准确地告诉我"value"字段相对于AtomicInteger对象的起始内存地址的字节相对偏移量。

        private volatile int value;
     
        /**
         * Creates a new AtomicInteger with the given initial value.
         *
         * @param initialValue the initial value
         */
        public AtomicInteger(int initialValue) {
            value = initialValue;
        }
     
        /**
         * Creates a new AtomicInteger with initial value {@code 0}.
         */
        public AtomicInteger() {
        }
    

      

    value是一个volatile变量,不同线程对这个变量进行操作时具有可见性,修改与写入操作都会存入主存中,并通知其他cpu中该变量缓存行无效,保证了每次读取都是最新的值


    找到sun.misc.Unsafe.java:

    /**
     * Atomically update Java variable to <tt>x</tt> if it is currently
     * holding <tt>expected</tt>.
     * @return <tt>true</tt> if successful
     */
    public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
    

      


    继续查找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 , 这个本地方法的最终实现在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011openjdkhotspotsrcoscpuwindowsx86vm atomicwindowsx86.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
      }
    }
    

      


    如上面源代码所示,用嵌入的汇编实现的, CPU指令是 cmpxchg,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg).反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果).lock前缀的作用说明:1禁止该指令与之前和之后的读和写指令重排序,2把写缓冲区中的所有数据刷新到内存中。
    总的来说,Atomic实现了高效无锁(底层还是用到排它锁,不过底层处理比java层处理要快很多)与线程安全(volatile变量特性),CAS一般适用于计数;多线程编程也适用,多个线程执行AtomicXXX类下面的方法,当某个线程执行的时候具有排他性,在执行方法中不会被打断,直至当前线程完成才会执行其他的线程。

    参考文章:http://www.infoq.com/cn/articles/java-memory-model-5

                        http://hllvm.group.iteye.com/group/topic/37940

                        http://www.cnblogs.com/dolphin0520/p/3920373.html
    ---------------------
    原文:https://blog.csdn.net/u013404471/article/details/47297123

    下面在看java并发编程艺术时有一段关于CAS的实现! 

    在Java中可以通过锁和循环CAS的方式来实现原子操作。

    (1)使用循环CAS实现原子操作
      JVM中的CAS操作正是利用了处理器提供的CMPXCHG指令实现的。自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止,以下代码实现了一个基于CAS线程安全的计数器方法safeCount和一个非线程安全的计数器count。

      从Java 1.5开始,JDK的并发包里提供了一些类来支持原子操作,如AtomicBoolean(用原子方式更新的boolean值)、AtomicInteger(用原子方式更新的int值)和AtomicLong(用原子方式更新的long值)。这些原子包装类还提供了有用的工具方法,

      比如以原子的方式将当前值自增1和自减1。

    package com.odyun;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class CASTest {
    
        private AtomicInteger atomicI = new AtomicInteger(0);
        private int i = 0;
        public static void main(String[] args) {
            final CASTest cas = new CASTest();
            List<Thread> ts = new ArrayList<Thread>(600);
            long start = System.currentTimeMillis();
            for (int j = 0; j < 100; j++) {
                Thread t = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 10000; i++) {
                            cas.count();
                            cas.safeCount();
                        }
                    }
                });
                ts.add(t);
            }
            for (Thread t : ts) {
                t.start();
            }
    // 等待所有线程执行完成
            for (Thread t : ts) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(cas.i);
            System.out.println(cas.atomicI.get());
            System.out.println(System.currentTimeMillis() - start);
        }
        /** * 使用CAS实现线程安全计数器 */
        private void safeCount() {
            for (;;) {
                int i = atomicI.get();
                boolean suc = atomicI.compareAndSet(i, ++i);
                if (suc) {
                    break;
                }
            }
        }
        /**
         * 非线程安全计数器
         */
        private void count() {
            i++;
        }
    }
    

      

  • 相关阅读:
    前台提交数据到后台还回错误提示并且把新增页面关掉的问题
    combox二级联动带不出来问题
    数据库设计字段如果首字母是大写
    带图片的修改后台出现的问题
    weblogic启动报错
    强制删除文件夹
    jQuery常用方法
    json数据的转换
    SQL DEFAULT 约束
    SQL CHECK 约束
  • 原文地址:https://www.cnblogs.com/jiuya/p/10368129.html
Copyright © 2011-2022 走看看