聊一聊Atomic类为什么是原子性的

5 篇文章 0 订阅

在此之前准备两个例子

Demo1:

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

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

    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        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(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }

    private static void add() {
        count.incrementAndGet();
    }

运行结果:5000

Demo2:

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

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

    public static int count = 0;

    public static void main(String[] args) throws Exception {
        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(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        count++;
    }

运行结果:4998

在这里可以看出Demo1和Demo2的区别仅仅在于count的类型,是基本类型int还是我们的Atomic包下的AtomicInteger。

而运行结果中,Demo1的结果是准确的,达到了我们预期的5000,而Demo2似乎有一些偏差,多次运行Demo2可看出输出结果大部分于5000以下并且结果不稳定。这是什么原因造成的,这还需要从jvm的结构模型讲起。

首先放一张来自于网络上的图

结合Demo2对这张图进行讲解。流程如下

1、在某一时刻类型为int的count=200在主内存中,主内存是对所有工作内存可见的。

2、此时有200个同时线程对这个count值进行并发操作,这时候每一个线程都把主内存中的count=200进行read,load到该线程的工作内存中,这是所有线程中的工作线程拷贝了一份count的副本,工作内存对彼此之间是不可见的

3、着每个java线程use其工作线程中count的副本到引擎中进行对count的加1操作,此时每个线程count副本的值是201

4、每个java线程再把count副本assign到工作线程中,然后在进行store,write到主内存中。

5、主内存经过了200个线程的依次write,得到的值为201,而我们预期的结果为400。

这很好的解释了为什么Demo2中会出现数据实际结果小于预期结果的原因。

 

那么到底哪里出错了呢?问题就在于工作内存不可见。你可能会说那这好办,我给int加一个volatile让数据被修改后立即对其他线程可见不就得了?

这里需要简单的解释一下volatile的可见是对于主内存可见,线程A操作完count时count=201立即刷新到主内存,可是线程B在刷新内存之前就已经被load到线程B的工作内存中了,这样还是不能解决问题。

所以volatile是不能够解决原子性的问题的

那么Demo1中的Atomic是如何解决原子性问题的呢?让我们按照Demo1的流程,点开源码看看

//截取本文所需要的关键方法
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    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;

    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    public final int get() {
        return value;
    }
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
}

首先初始化:

在初始化我们count的时候实际上是把我们传入的初始值0赋予一个AtomicInteger的一个字段value。

这个value有啥用呢?在incrementAndGet中好像没有看见对value的操作。

我们看看初始化静态代码块做了什么事情,通过unsafe类直接对内存进行操作得到AtomicInteger 字节码中字段名为“value”的相对于内存地址的偏移量。可以理解为,这个操作是直接获取主内存中AtomicInteger类中value的内存地址,我们根据这个内存地址可以直接得到value的值。进行了这些初始化后我们来了几个线程对count进行incrementAndGet操作。

下面是调用unsafe的方法对传入值count进行+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;
    }

这里典型的就是一个CAS操作,对于非预期值会进行自旋直到符合预期值才会进行加1然后返回,这样可以保证我们想要加1的count永远都是一个最新的值,这样可以保证原子性操作。

让我们重新看看开始的那张图,看看AtomicInteger是怎么操作的

1、当某个时刻主内存中count的值为200,然后同时被200个线程read、load到工作内存中。

2、线程1打算对count进行加1操作,根据count值所在的地址直接得到一个的值count1=200,然后对于这个count1进行一次cas操作,看count1是否被其他线程改变过而导致内存地址对应的值不为预期的值,这里发现count1没有被修改,于是count1+1,刷新到主内存。

3、此刻线程2也打算对count进行加1,操作和步骤2类似,此时得到的count1也为200,就在这时线程1操作完成,目标地址对应的值变为201,然后线程2的count1=200进行cas操作,通过比较发现200!=201,无法进行加1操作,于是线程2再次循环重新通过内存地址获得count1的值为201,在进行cas发现符合条件进行加1操作。

4、其他线程同理,每当发现我要操作的count值不为预期值则进行自旋操作。

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值