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

浙公网安备 33010602011771号