zoukankan      html  css  js  c++  java
  • 关于AtomicInteger里面addAndGet如何保证同步的(compareAndSwapInt原理)

    先看到类的开头,只看static代码块和value声明

    public class AtomicInteger extends Number implements java.io.Serializable {
        private static final long serialVersionUID = 6214790243416807050L;
    
        // setup to use Unsafe.compareAndSwapInt for updates
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    
        private volatile int value;
    
        /**
         * Creates a new AtomicInteger with the given initial value.
         *
         * @param initialValue the initial value
         */
        public AtomicInteger(int initialValue) {
            value = initialValue;
        }
        ............................
    }

    比如我们使用new AtomicInteger(1);就会加载类,static静态代码块执行。使用的反射的机制得到名字是value的Field对象,再根据objectFieldOffset这个方法求出value这个变量在该对象内存中的偏移量valueOffset 

    public final int addAndGet(int delta) {
            return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }

    这个方法究竟是怎么保证线程安全的呢?

    控制线程安全的其实就是乐观锁。
    有人用jad反编译后得到

    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }
    public native int getIntVolatile(Object o, long offset);
    public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
    

    getIntVolatile方法和compareAndSwapInt方法是看不到源码的。经过代码测试才知道compareAndSwapInt这几个参数的意思。

    此处转载请注明https://blog.csdn.net/qq_34115899

    经过我的测试,比如

    AtomicInteger t = new AtomicInteger(1); // ①

    int ans = t.addAndGet(2); // ②

    System.out.println(ans); // ③,得到的结果就是3,为什么呢?

    看到这个方法compareAndSwapInt(o, offset, v, v + delta);

    第一个参数为这个AtomicInteger对象

    第二个参数为刚刚的偏移量,这个偏移量就是AtomicInteger对象的value的地址

    第三个参数就是v = getIntVolatile(o, offset);,这个v是从主存中得到的值

    第四个参数是将v+delta=1+2=3,为了更新对象中的value值

    那么这个方法到底在干什么呢?

    首先由第一个第二个参数(对象和偏移量)确定了这个AtomicInteger对象的值value=1,然后比较从主存中得到的值v=1,v==value?如果相等,那么执行value=v+delta=1+2=3,因为AtomicInteger对象中的value是volatile修饰,会立马刷新到主存value=3,并且让其他的线程的工作内存的值失效,其他线程获取value也只能从主存获取,然后返回true,跳出循环,返回v=1,然后外层调用的函数还会继续加上delta,就会返回1+2的值3。

    如果v!=value,那么不执行v+delta,并且返回false,循环继续执行,这种情况可能是多个线程同时在更改这个AtomicInteger对象,此时说明主存中的值v和对象中的value不一样。

    还有一种情况也会返回false,那就是compareAndSwapInt方法第一次执行返回true,如果没有在主存中读取值,也就是没执行getIntVolatile方法,那么往后多次一直返回false,直到调用getIntVolatile方法之后再执行一次才会返回true。

    具体情境分析:

    AtomicInteger t = new AtomicInteger(1); // ①

    ......

    int ans = t.addAndGet(2); // ②

    ......

    System.out.println(ans); // ③,得到的结果就是3,为什么呢?

    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }

    AtomicInteger对象由两个线程共享,同时执行②操作。Thread1执行到乐观锁条件compareAndSwapInt(o, offset, v, v + delta)的同时,Thread2也执行到这里,他们都获取了v=1,此时Thread1执行成功,返回true,跳出循环。

    Thread2执行中,如果Thread1已经更新了value值,那么v和value不相等,返回false,继续循环,如果还没有更新value值,v==value成立,但是因为是第二次执行compareAndSwapInt,所以仍然返回false,继续循环。再从主存重新获取v值为3,然后判断根据偏移量获取value地址再取出值,发现v==value成立,第一次执行,返回true,并且会把v+delta=3+2=5刷新到主存,然后返回v=3,外层还会再加delta,也就是3+2,最后返回5。整个过程利用乐观锁实现了线程安全。

    两个线程执行了t.addAndGet(2);最后返回为5,而不会是3。

    关于为什么compareAndSwapInt第一次返回true,第二次会返回false的测试代码,自行体会。

    import java.lang.reflect.Field;
    
    import sun.misc.Unsafe;
     
    public class Main {
        
        static class Target{
            public int value = 10;
        }
        
        public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
            //通过反射获得Unsafe实例,仅BootstrapClassLoader加载的类
            //($JAVA_HOME/lib目录下jar包包含的类,如java.util.concurrent.atomic.AtomicInteger)
            //才能通过Unsafe.getUnsafe静态方法获取
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Unsafe unsafe = (Unsafe) field.get(null);
            
            //获得Target实例域value
            Field valueField = Target.class.getDeclaredField("value");
            //实例化Target
            Target t = new Target();
            System.out.println("原始value值:" + valueField.get(t));
            
            //获得实例域在class文件里的偏移量
            final long valueOffset = unsafe.objectFieldOffset(valueField);
            int v5 = unsafe.getIntVolatile(t, valueOffset);// 从主存获取
            //第一次swap
            System.out.println("第一次swap(10,20)函数返回值:" + unsafe.compareAndSwapInt(t, valueOffset, v5, v5+5));
            System.out.println("第一次swap(10,20)后value值:" + valueField.get(t));
                    
          //第二次swap
            System.out.println("第2次swap(10,20)函数返回值:" + unsafe.compareAndSwapInt(t, valueOffset, v5,v5+5));
            System.out.println("第2次swap(10,20)后value值:" + valueField.get(t));  
            v5 = unsafe.getIntVolatile(t, valueOffset);
          //第3次swap
            System.out.println("第3次swap(10,20)函数返回值:" + unsafe.compareAndSwapInt(t, valueOffset, v5,v5+5));
            System.out.println("第3swap(10,20)后value值:" + valueField.get(t));    
        }
    }

    运行结果:

    原始value值:10
    第一次swap(10,20)函数返回值:true
    第一次swap(10,20)后value值:15
    第2次swap(10,20)函数返回值:false
    第2次swap(10,20)后value值:15
    第3次swap(10,20)函数返回值:true
    第3swap(10,20)后value值:20

    =======================Talk is cheap, show me the code========================

    CSDN博客地址:https://blog.csdn.net/qq_34115899
  • 相关阅读:
    笔试-2020软件工程师Java(上海)中科创达(收获很多,自己基础还是不行)
    SpringCloud-Spring Cloud 2 Finchley.M9报错问题
    IDEA 实体类生成serialVersionUID
    idea创建maven项目时出现Unable to import maven project: See logs for details
    Eclipse可以执行jsp文件却无法访问Tomcat主页
    已知n个正数:wi, 1<=i<=n, 和M。要求找出{wi }的所有子集使得子集内元素之和等于M。例如: n=4, (w1,w2,w3,w4)=(11,13,24,7),M=31 则满足要求的子集是(11,13,7)和(24,7)。
    嵌入式系统外部中断实验(按下按键,LED灯依次熄灭)
    嵌入式系统按键实现(按下按钮,LED灯熄灭)
    如何跳转一个由两个框架组成的页面
    对某个页面的过滤
  • 原文地址:https://www.cnblogs.com/lcy0515/p/10807846.html
Copyright © 2011-2022 走看看