美文网首页
AtomicInteger

AtomicInteger

作者: 云师兄 | 来源:发表于2019-02-24 19:54 被阅读17次

1. 引子

在之前《CountDownLatch和Semaphore》一章最后的例子留下了一个问题,即多线程并发访问时出现不一致的线程不安全问题,为此本节将对其进行修改以满足线程安全的要求,并引入atomic包中的AtomicInteger类的相关介绍。

2. 使用AtomicInteger确保一致性

对示例代码修改如下:

public class ConcurrencyTest {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    // public static int count = 0; // 存在线程安全问题
    public static AtomicInteger count = new AtomicInteger(0);

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

        // 创建线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        // 信号量
        final Semaphore semaphore = new Semaphore(threadTotal);

        // 计数器闭锁
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);

        for (int i=0;i<clientTotal;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (Exception e){
                        System.out.println(e);
                    }
                    // 每个线程执行时计数器都减1
                    countDownLatch.countDown();
                }
            });
        }

        System.out.println("countDownLatch is awaiting!");
        countDownLatch.await();

        executorService.shutdown();
        System.out.println("count="+count.get());
    }

    public static void add(){
        //count ++;
        count.getAndIncrement();
    }
}

从上述示例可知,我们修改了如下内容:

  • 修改count变量从int改为AtomicInteger。
  • count自加由count++改为 count.getAndIncrement();
    在改之前可知由于具有一致性问题导致最终结果不是理想中的5000,经过修改后,每次结果都符合预期,即5000的值。那么AtomicInteger究竟是如何解决一致性问题的呢,下面对其进行分析。

3. AtomicInteger源码分析

要想理解其实现原理,主要分析下count.getAndIncrement();方法实现,源码如下:

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

实现主要调用了Unsafe类的getAndAddInt()方法,传入第一个参数为当前对象count,第一个参数为当前对象的值,比如当前循环中count值为2,第三个参数为每次递增1。进入这个方法:

   public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2); 
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
    public native int getIntVolatile(Object var1, long var2);
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

getAndAddInt()方法中涉及到的两个方法调用如上都定义为native,即java底层实现的本地方法(JNI),这里就不深入查看实现了,主要说下做了什么:

  • getIntVolatile()方法:获取保存当前对象count的主存地址的引用(注意不是对象的值,是引用)。
  • compareAndSwapInt()方法:比较当前对象的值和底层该对象的值是否相等,如果相等,则将当前对象值加1,如果不相等,则重新去获取底层该对象的值,这个方法的实现就是CPU的CAS(compare and swap)操作。
  • 写到这里我一直有个疑问,那就是这个AtomicInteger和volatile有什么区别呢?在之前的学习中我们可知volatile具有一致性,但是不具备原子性,但是AtomicInteger却号称同时具备一致性和原子性,这是什么原因呢?我又看了下AtomicInteger源码中实际保存数据的变量定义:private volatile int value;。原来AtomicInteger内部实现就使用了volatile关键字,这就解释了为什么执行CAS操作的时候,从底层获取的数据就是最新的数据,如果当前要保存的值和内存中最新的值不相等的话,说明在这个过程中被其他线程修改了,只能获取更新当前值为最新值,再那这个当前值再去和重新去内存获取的最新值比较,直到二者相等的时候,才完成+1的过程。
  • 使用AtomicInteger的另外一个好处在于,它不同于sychronized关键字或lock用锁的形式来实现原子性,因为加锁会影响性能,而是采用循环比较的形式来提高性能。

相关文章

网友评论

      本文标题:AtomicInteger

      本文链接:https://www.haomeiwen.com/subject/giybyqtx.html