小陈:老王啊,今天就要开始Atomic原子类的学习了吧......
老王:是啊,之前我们只是简单介绍了Atomic的体系,今天我们就要进入Atomic底层原理的的学习了,首先我们从AtomicInteger这个比较简单的原子类开始,在说AtomicInteger的底层原理之前呢,我先给你看两个例子:
实测样例对比Integer和AtomicInteger的线程安全性
Integer的测试样例
(1)定义一个共享变量Integer
(2)定义一个新的线程类,创建两个线程,每个线程执行10000次value++ 操作
public class AddDemo { // 定义一个Integer类型的共享变量value private static Integer value = 0; public static class AddThread extends Thread{ @Override public void run() { for (int i = 0; i < 10000; i++) { value++; } } } public static void main(String[] args) throws InterruptedException { // 定义两个线程各自对value执行10000次自增操作 AddThread addThread1 = new AddThread(); AddThread addThread2 = new AddThread(); // 启动两个线程 addThread1.start(); addThread2.start(); // 主线程等待两个线程执行完毕 addThread1.join(); addThread2.join(); // 输出最新的value结果 System.out.println("value的值为:" + value); } }
看看最后得到的结果19513,比预期20000相差还是挺大的
AtomicInteger的测试样例
(1)定义一个AtomicInteger原子类
(2)定义一个新的线程类AtomicAddThread,创建两个线程,每个线程执行10000次incrementAndGet() 操作
public class AtomicAddDemo { private static AtomicInteger value = new AtomicInteger(0); public static class AtomicAddThread extends Thread{ @Override public void run() { for (int i = 0; i < 10000; i++) { value.incrementAndGet(); } } } public static void main(String[] args) throws InterruptedException { // 定义两个线程各自对value执行10000次自增操作 AtomicAddThread atomicAddThread1 = new AtomicAddThread(); AtomicAddThread atomicAddThread2 = new AtomicAddThread(); // 启动两个线程 atomicAddThread1.start(); atomicAddThread2.start(); // 主线程等待两个线程执行完毕 atomicAddThread1.join(); atomicAddThread2.join(); // 输出最新的value结果 System.out.println("value的值为:" + value.get()); } }
实际的结果20000,与预期的结果准确无误
老王:小陈啊,通过上述的实际例子,说明 AtomicInteger原子类确实是线程安全的。
小陈:是啊,使用AtomicInteger两个线程执行20000次自增操作得到的结果于预期值一致,那AtomicInteger底层到底是怎么确保线程安全的呢?
老王:这个啊,我们慢慢来剖析......
AtomicInteger的内部属性
老王:我们先通过源码来看一下AtomicInteger内部有哪些属性以及作用是什么:
public class AtomicInteger extends Number implements java.io.Serializable { // unsafe对象,可以直接根据内存地址操作数据,可以突破java语法的限制 private static final Unsafe unsafe = Unsafe.getUnsafe(); // 存储实际的值 private volatile int value; // 存储value属性在AtomicInteger类实例内部的偏移地址 private static final long valueOffset; static { try { // 在类初始化的时候就获取到了value变量在对象内部的偏移地址 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } }
(1)首先内部持有一个unsafe对象,Atomic原子类底层的操作都是基于unsafe对象来进行的
(2)然后有一个volatile int value变量,这个value就是原子类的实际数值,使用volatile来修饰,volatile可以保证并发中的可见性和有序性(这里之前讲过volatile可以保证可见性和有序性,不记得的要回去重新看一下哦)
(3)还有一个valueOffset,看看这段代码,其实就是获得value属性在AtomicInteger对象内部的偏移地址的:
valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value"));
这个value属性相对于AtomicInter对象的内部偏移量存储在valueOffset中,我们之前讲过的 ,通过unsafe类是直接在内存级别去给变量赋值的。这里啊,我们再回顾一下unsafe值怎么从内存级别操作数据的:
- 首先要知道你要操作对象的内存地址,也就是AtomicInteger对象引用指向的内存地址
- 其次是要知道value属性在对象内部的偏移量offset,就可以通过 (对象地址 + offset偏移量) 直接找到value变量在内存的地址是多少,然后就可以直接给这块内存赋值了。
小陈:额,这个AtomicInteger内部还是蛮简单的呀,一个 volatile int value的属性、一个unsafe类、一个偏移地址就完事了
老王:哈哈,是啊,其实Atomic原子类啊,就是对基础的类型进行了一下包装而已,使得他们是线程安全的。比如AtomicInteger要对int进行包装,所以它内部肯定是有一个属性来存储int的值的。至于它其他两个属性valueOffset、unsafe是辅助实现并发安全的属性。
AtomicInteger的构造方法
老王:让我们再来看看AtomicInteger的构造方法源码:
public AtomicInteger(int initialValue) { value = initialValue; } public AtomicInteger() { }
提供了两个构造方法,第一个是在创建AtomicInteger对象的时候直接给内存存储值的volatile int value设置初始化的值;第二个没有赋初始值,那默认就是0
AtomicInteger方法的源码分析
getAndIncrement()方法源码
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
我们看到AtomicInteger的getAndIncrement()方法源码很简单,底层就是基于unsafe.getAndAddInt包装了一下,让我们继续看一下unsafe.getAndAddInt方法源码:
public final int getAndAddInt(Object o, long valueOffset, int x) { int expected; do { expected = this.getIntVolatile(o, valueOffset); } while(!this.compareAndSwapInt(o, valueOffset, expected, expected + x)); return expected; }
(1)首先 (o + valueOffset) 得到value变量在内存中的地址,然后根据地址直接取出value在主内存值,这个值记录为expected中
(2)根据 (o + offsetSet)地址偏移量,expected期待的值跟当前内存的值进行对比,如果相等则CAS操作成功,内存的值修改为 expected + x
(3)如果值不相等,则进入下一次循环,直到CAS操作成功为止。
(4)由于使用了volatile 修饰符修饰了value,所以一旦修改了别的线程能立马可见、同时volatile还是用内存屏障确保有序性
(5)所以上面的CAS操作确保了原子性,通过volatile确保可见性、有序性;线程安全的三个特性都满足了,上面的操作就是线程安全的。
小陈:原来这里AtomicInteger底层执行getAndIncrement() 操作底层就是直接调用unsafe的getAndAddInt()方法啊,最后还是走到了unsafe的compareAndSwapInt方法里面了,这里还是简单的呀。
老王:哈哈,AtomicInteger底层的源码本来就是不难的,底层都是基于unsafe进行薄薄的包装了一层而已,然后底层都是基于unsafe的CAS操作来保证原子性的,然后有使用volatile来修饰变量,保证了可见性和有序性,这样它就是线程安全的。
老王:关于unsafe的CAS操作是怎么保证原子性的,小陈你还记得住不,前两章的时候我们还画了一个图的:
小陈:嗯嗯,这个我记得的。
老王:好,那我也就不在CAS怎么保证原子性的话题上多说的了,我们继续看AtomicInteger原子类的其它源码:
AtomicInteger的compareAndSet源码:
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
底层也还是直接调用unsafe的compareAndSwapInt方法直接去修改,不过这里不同的是,只会执行一次CAS操作,即使失败了也不会重复CAS
其它方法源码:
其它的方法,基本都是直接调用unsafe.getAndInt方法,上面我们分析过了
public final int getAndDecrement() { return unsafe.getAndAddInt(this, valueOffset, -1); } public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); }
老王:好了,AtomicInteger的源码基本就分析到这里了,小陈关于AtomicInteger的底层原理这块,你还有其它的疑问不?
小陈:基本上没有了,AtomicInteger的底层还是比较简单的,基本都是调用unsafe的CAS操作确保原子性,然后使用volatile修饰变量,确保可见性和有序性,我理解上应该没问题了。
老王:好的,那我们就进入下一个原子类AtomicBoolean的讨论
AtomicBoolean 底层原理分析
AtomicBoolean 属性
public class AtomicBoolean implements java.io.Serializable { // unsafe对象,可以直接根据内存地址操作数据,可以突破java语法的限制 private static final Unsafe unsafe = Unsafe.getUnsafe(); // 存储实际的值 private volatile int value; // 存储value属性在AtomicInteger类实例内部的偏移地址 private static final long valueOffset; static { try { // 在类初始化的时候就获取到了value变量在对象内部的偏移地址 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } }
小陈:啊这AtomicBoolean拥有的属性怎么跟AtomicInteger是一模一样的!! ,它不是布尔类型吗?怎么使用一个int类型的 volatile int value来存储?
老王:其实啊,这只是AtomicBoolean 玩的一个小把戏,我们接着看就知道了:
我们看一下AtomicBoolean的构造函数源码:
public AtomicBoolean(boolean initialValue) { // 当传入initialValue为true的时候value = 1 , false的时候value = 0 value = initialValue ? 1 : 0; }
所以这里我们猜测,AtomicBoolean 底层就是使用一个int类型来表示true和false的,当value = 1的时候表示true,当value = 0的时候表示false。
然后继续看一下get()的源码:
直接就是判断value != 0 , 当value = 1则返回true,value = 0 返回false,证明了上面的猜想
public final boolean get() { return value != 0; }
然后再看一下AtomicBoolean最常用最重要的方法compareAndSet源码:
public final boolean compareAndSet(boolean expect, boolean update) { int e = expect ? 1 : 0; int u = update ? 1 : 0; return unsafe.compareAndSwapInt(this, valueOffset, e, u); }
底层就是将true 转成 1,将false转成 0,然后还是调用unsafe的compareAndSwapInt方法去执行CAS操作!!, 这个我们在上面将AtomicInteger的时候已经讲过了
小陈:哎呀,原来是这样啊,这个AtomicBoolean 耍花样啊,我还以为它底层使用布尔类型来存储值呢,哪知道这兄弟直接volatile 修饰的int类型,然后1 表示 true,0 表示false,这操作不都跟AtomicInteger一样吗?只是将value表示的意思换了一下而已......
老王:是啊,看过AtomicBoolean的底层源码之后恍然大悟了吧,很多功能啊其实实现起来没有那么难,还是有很多的方式的.....
小陈:恩恩,这个我认同......
老王:小陈啊,今天我们将AtomicInteger、AtomicBoolean 的底层原理就到这里了,我们明天继续......
小陈:我们下一章见。