关于AtomicInteger里面addAndGet如何保证同步的(compareAndSwapInt原理)

2023-05-06 19:17:41 浏览数 (1)

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

代码语言:javascript复制
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 

代码语言:javascript复制
public final int addAndGet(int delta) {
         return unsafe.getAndAddInt(this, valueOffset, delta)   delta;
 }

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

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

代码语言:javascript复制
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。

具体情境分析:

代码语言:javascript复制
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的测试代码,自行体会。

代码语言:javascript复制
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

0 人点赞