AtomicInteger源码解读和Unsafe对象

发布时间 2023-10-08 17:09:20作者: 程序晓猿

针对线程安全问题,jdk除提供了加锁的解决方式外还提供了无锁的方式,例如AtomicInteger 这个原子整数类,

无锁并发的线程安全是通过cas来实现的,这一篇文章就来简单分析下AtomicInteger 的源码实现。

一、AtomicInteger的简答使用

先来看一断非线程安全的代码

@Slf4j
public class ThreadTest2 {

    static int count=0;

    public static void main(String[] args) throws InterruptedException {
        /**
         * 有一个静态变量count,两个线程分别对其进行相等次数的+1和-1操作,
         * 因为++和--操作本身不是原子的,所以最终打印出的res可能不是0,
         * 为了看到效果让这段代码整体重复运行100次
         */
        for (int k = 0; k < 100; k++) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        count++;
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        count--;
                        try {
                            Thread.sleep(2);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();

            log.info("res:{}",count);
        }
    }
}

为了解决上述代码中多个线程对变量count同时操作的线程安全问题,可以使用加锁的方式去解决,但jdk也提供了一种无锁的方式,即使用原子整数AtomicInteger 来作为count,我们先来看下怎么使用

@Slf4j
public class ThreadTest2 {

    //使用AtomicInteger作为计数器
    static AtomicInteger count= new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        /**
         * 使用AtomicInteger作为计数器,两个线程分别对其进行相等次数的+1和-1操作,
         * 因为++和--操作本身不是原子的,所以最终打印出的res可能不是0,
         * 为了看到效果让这段代码整体重复运行100次
         */
        for (int k = 0; k < 100; k++) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        //原子整数的自增操作
                        count.getAndIncrement();
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        //原子整数的自减操作
                        count.getAndDecrement();
                        try {
                            Thread.sleep(2);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();

            log.info("res:{}",count);
        }
    }
}

上边的代码中多线程同时调用原子整数类提供的自增和自减操作,由原子整数类保证了线程安全,即

getAndIncrement/getAndDecrement这两个操作本身就是原子的。

二、 AtomicInteger源码解读

为什么AtomicInteger的自增和自减操作是原子的可以保证线程安全呢?这是因为它使用了Unsafe 对象

我们来看一下它的源码

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    // Unsafe对象是jdk提供的,其中提供了一些直接操作内存的方法和一些cas方法可以用来控制线程安全
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            // 这里是用Unsafe获取当前类中value这个属性的内存偏移量,
            //可以理解成每个对象都会有一个内存地址,而对象中属性的内存地址相对对象本身的偏移量是固定的,
            // 知道了一个对象的内存地址,再加上某个属性的地址偏移量就能定位到某个具体的属性,
            //这里获取这个内存偏移量是为了后续通过Unsafe类直接操作这个属性
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    // 原子整数类的value属性
    private volatile int value;
    
    //构造方法
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    
    //自增操作,可以看到是直接调用的unsafe类的方法,传递了当前对象,内存偏移量,
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    
    //自减操作
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }
}

从源码可以看到原子整数类中实现原子自增的关键代码就是调了unsafe类的方法,所以有必要再看下这个类

三、Unsafe类

Unsafe类是java中的一个比较底层的类,其中包含了一些比较底层的操作,例如直接操作内存,cas操作,线程操作等。因为这是一个比较底层的类,所以jdk不允许我们直接使用它,要获取到它的对象只能通过反射来获取到,我们先看下它的源码,了解下上边提到的unsafe.getAndAddInt方法

部分源码,这个类是sun包下的一个类,从这个包名也能看出是一个比较底层的类,如非必要不要自己去使用它。

原子整数类的线程安全是通过unsafe提供的cas操作完成的,我们先看下它提供的原子操作方法,

getAndAddInt方法就是通过调用cas操作+自旋的方式保证线程安全

package sun.misc;

public final class Unsafe {
    //通过这个静态属性就可以获取到这个类的对象,只能通过反射来获取
    private static final Unsafe theUnsafe;
    
    //这三个方法是unsafe类提供的cas操作,是通过本地方法实现的
    //cas的意思是比较并设置值,参数一般会有一个期望旧值,将要设置的目标值,
    // 方法执行时会先判断变量的原始值是否和期望值一样,如果是就更新成目标值然后返回true,
    //如果变量的原始值和期望值不一样就返回false,
    //这个方法是通过本地方法(最终通过cpu指令)保证了上边这个比较并设置值的过程是原子操作
    /**
    * obj: 要修改那个对象的属性
    * offset: 对象属性相对于对象的内存偏移量
    * expect: 期望的旧值
    * newVal: 要设置成的目标值
    */
    public final native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object newVal);

    public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int newVal);

    public final native boolean compareAndSwapLong(Object obj, long offset, long expect, long newVal);
    
    //这是自增的方法,注意这个方法是先返回原来的值再自增的,类似i++这样的操作
    public final int getAndAddInt(Object obj, long offset, int step) {
        int var5;//这是对象属性的原始值
        do {
            //调用本类中直接操作内存的方法来获取对象obj的内存偏移量是offset的属性的最新值,
            //针对原子整数AtomicInteger就是获取value属性的最新值
            var5 = this.getIntVolatile(obj, offset);
            //这块就是调用cas方法设置对象属性的值,有可能成功或者失败,
            //如果失败就再次循环重新获取旧值进行更新的操作,直到更新成功退出循环
        } while(!this.compareAndSwapInt(obj, offset, var5, var5 + step));

        return var5;
    }
    
}

总结下getAndAddInt方法就是通过这样的cas操作+自旋的方式保证了外界调用的一次自增/自减操作一定是在最新值对象属性值的基础上进行的,这样就保证了这次操作的原子性,保证线程安全。

四、尝试使用Unsafe保证线程安全

这一节我们尝试下直接使用Unsaft类中的cas操作来保证自增/自减操作的原子性。

首先创建一个计数器类

public class Counter {
    // volatile保证多线程可见行
    public volatile int count;
    
    public Counter(int count) {
        this.count = count;
    }
}

测试类

@Slf4j
public class ThreadTest2 {

    //使用Counter类作为计数器
    static Counter counter = new Counter(0);

    public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {

        //利用反射获取Unsafe对象
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);//静态变量获取时传null
        //获取Counter中count属性的内存偏移量
        long offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count"));

        for (int k = 0; k < 100; k++) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        //自己实现cas+自旋的操作
                        boolean res=false;
                        while (!res) {
                            //获取旧值
                            int old = unsafe.getIntVolatile(counter,offset);
                            //cas设置新值,得到cas操作结果
                            res = unsafe.compareAndSwapInt(counter,offset,old,old+1);
                        }
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        //自己实现cas+自旋的操作
                        boolean res=false;
                        while (!res) {
                            //获取旧值
                            int old = unsafe.getIntVolatile(counter,offset);
                            //cas设置新值,得到cas操作结果
                            res = unsafe.compareAndSwapInt(counter,offset,old,old-1);
                        }
                        try {
                            Thread.sleep(2);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();

            log.info("res:{}", counter.count);
        }
    }
}

这段代码中关键是使用cas操作实现自增和自减