AtomicInteger底层原理

前言:为什么底层使用CAS而不是sychronized?

首先需要了解的是,Java中只对基本类型的变量的赋值和读取是原子操作,如 i=1 这样的是原子操作,j=i,j++都不是原子操作,因为他们都进行了多次原子操作,那么AtomicInteger 就应运而生了,它是一个原子类,可以解决我们在多线程环境下 i++的线程安全问题。那既然是解决线程安全问题的,那为什么不用sychronized呢,而是使用CAS呢?带着这个问题,我们开始接下来的源码分析:

剖析:

一、使用以及原理

1、我们先抛出一个线程不安全的例子:结果:13400


public class UnsafeInt {

    static int number=0;

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

        Runnable runnable=new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    number=number+1;
                }
            }
        };

        Thread t1=new Thread(runnable);
        t1.start();
        Thread t2=new Thread(runnable);
        t2.start();

        t1.join();
        t2.join();
        System.out.println("number:"+number);

    }
}

2、那AtomicInteger是如何解决这个问题的呢?拥有这个API你就行

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

变量valueOffset,表示该变量在内存中的偏移地址,Unsafe就是根据内存偏移地址获取数据的。

    private volatile int value;

变量value用volatile修饰,保证了多线程之间的内存可见性。

3、我们试一下这个API是否可行:结果:20000

public class TestInt {

    static AtomicInteger number=new AtomicInteger(0);

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

        Runnable runnable=new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    number.incrementAndGet();
                }
            }
        };

        Thread t1=new Thread(runnable);
        t1.start();
        Thread t2=new Thread(runnable);
        t2.start();

        t1.join();
        t2.join();
        System.out.println("number:"+number);

    }
}

4、那么既然这么香,原理是什么?脑海里浮现valaile,sychronized,lock等等,都不是,其实是Unsafe

    // 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;

Unsafe,是CAS的核心类,由于Java方法无法像C,C++那样直接访问底层系统,但是JVM为我们提供了一个后门,就是Unsafe,基于该类可以直接操作特定内存的数据。Unsafe存在于sun.misc中,为我们提供了硬件级别的原子操作。
你只需要记住一点:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应的任务,这个保证了AtomicInteger的原子性,CAS是底层思想。

二、缺点以及对应的解决方案

1、好的, 既然彻底了解了CAS,那么问题来了,当真这么完美毫无缺点吗?还真有
缺点如下:

a.循环时间长且开销很大
b.只能保证一个共享变量的原子操作
c.引出来ABA问题

对于B缺点,如果我们想保证多个变量的原子操作,只能用加锁解决了,锁可以保证一段方法内的变量安全性。

对于C缺点,可以引出一个经典的大厂面试题:原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?

在这里插入图片描述

Atomic是jdk提供的一系列包的总称,里面的分类有原子整数(AtomicInteger,AtomicLong,AtomicBoolean),原子引用(AtomicReference,AtomicStampedReference,AtomicMarkableReference),原子数组(AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray),更新器(AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater)。
用代码演示以下ABA的产生:

 System.out.println("=============以下是ABA问题的产生==============");
        new Thread(()->{
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
        },"t1").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100,2020)+"\t"+atomicReference.get());
        },"t2").start();

2、那么我们如何规避ABA问题呢?talk is cheap,show me the code,原子时间戳引用

  System.out.println("================以下是ABA问题的解决===============");
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第2次版本号:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第3次版本号:" + atomicStampedReference.getStamp());
        }, "t3").start();


        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = atomicStampedReference.compareAndSet(100, 2020, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + "\t修改成功否:" + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName() + "\t当前实际最新值" + atomicStampedReference.getReference());
        }, "t4").start();
    }

三、原子类进阶 LongAdder

JDK1.8之后,还有一个LongAdder,下期再说吧

至此正文内容就已结束了,感谢您看到这里。 如果本文对您有一点帮助,希望能得到您一个点赞👍哦 您的认可才是我写作的动力! 如果需要转载,请在开头标明原文地址。

posted @ 2020-11-12 11:33  海绵贝贝Erica  阅读(257)  评论(0)    收藏  举报