关于AtomicInteger里面addAndGet如何保证同步的(compareAndSwapInt原理)
发布日期:2021-06-30 19:56:12 浏览次数:2 分类:技术文章

本文共 5045 字,大约阅读时间需要 16 分钟。

先看到类的开头,只看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这几个参数的意思。

此处转载请注明

经过我的测试,比如

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

 

转载地址:https://liuchenyang0515.blog.csdn.net/article/details/83018870 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:141. Linked List Cycle(环形链表,判断链表中是否有环存在)
下一篇:哈希表中线性探测再散列法及等概率条件下平均查找长度试题分析

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年04月17日 22时03分17秒