AtomicInteger 源码学习

在高并发场景下,count++ 是线程不安全的,如果要采用这种计数的方法,应使用 Atomic包提供的AtomicInteger类。

例子演示:

public class AtomicIntegerTest {
    //初始化 线程总数
    public static int clientTotal = 5000;
    //初始化 同时执行的线程数
    public static int threadTotal = 100;
    //初始化 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++) {
            //lamda 表达式 使用线程池
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    //调用 +1 的方法
                    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();
    }
}

下面对 AtomicInteger 的 incrementAndGet()方法的源码进行阅读:

看到 返回的是一个 unsafe对象的getAndAddInt(this,valueoffset,1)+1;

/**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

再对 getAndAddInt()这个方法的实现进行查看

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;
    }
以 2+1为例子
这里  var1 是传回去的对象;var2 是要被操作的数的偏移量 offset;var4 则是要加的数,就是1。
 方法中定义了一个var5  getIntVolatile() 是一个native本地方法 获取obj对象中offset偏移地址对应的整型field的值

然后调用 compareAndSwapInt()  比较obj的offset处内存位置中的值和期望的值,
如果相同则更新。此更新是不可中断的 
obj(var1)需要更新的对象,
offset(var2)obj中整型field的偏移量;
expect(var 5) 希望field中存在的值;
update(var5+var4)如果期望值expect与field的当前值相同,设置filed的值为这个新值

下面总结引用该页面的话:https://www.erlo.vip/share/9/22976.html

首先介绍一下什么是Compare And Swap(CAS)?简单的说就是比较并交换。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。” Java并发包(java.util.concurrent)中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作。

 我们来分析下incrementAndGet的逻辑:

  1.先获取当前的value值

  2.调用compareAndSet方法来来进行原子更新操作,这个方法的语义是:

扫描二维码关注公众号,回复: 5266173 查看本文章

    先检查当前value是否等于obj中整型field的偏移量处的值,如果相等,则意味着obj中整型field的偏移量处的值 没被其他线程修改过,更新并返回true。如果不相等,compareAndSet则会返回false,然后循环继续尝试更新。

第一次count 为0时线程A调用incrementAndGet时,传参为 var1=AtomicInteger(0),var2为var1 里面 0 的偏移量,比如为8090,var4为需要加的数值1,var5为线程工作内存值,do里面会先执行一次,通过getIntVolatile 获取obj对象中offset偏移地址对应的整型field的值此时var5=0;while 里面compareAndSwapInt 比较obj的8090处内存位置中的值和期望的值var5,如果相同则更新obj的值为(var5+var4=1),此时更新成功,返回true,则 while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));结束循环,return var5。

当count 为0时,线程B 和线程A 同时读取到 count ,进入到第 8 行代码处,线程B 也是取到的var5=0,当线程B 执行到compareAndSwapInt时,线程A已经执行完compareAndSwapInt,已经将内存地址为8090处的值修改为1,此时线程B 执行compareAndSwapInt返回false,则继续循环执行do里面的语句,再次取内存地址偏移量为8090处的值为1,再去执行compareAndSwapInt,更新obj的值为(var5+var4=2),返回为true,结束循环,return var5。

CAS的ABA问题

  当然CAS也并不完美,它存在"ABA"问题,假若一个变量初次读取是A,在compare阶段依然是A,但其实可能在此过程中,它先被改为B,再被改回A,而CAS是无法意识到这个问题的。CAS只关注了比较前后的值是否改变,而无法清楚在此过程中变量的变更明细,这就是所谓的ABA漏洞。 

猜你喜欢

转载自blog.csdn.net/wjxhhh123/article/details/84336671
今日推荐