小陈:老王啊,今天就要开始Atomic原子类的学习了吧......
老王:是啊,之前我们只是简单介绍 了Atomic的体系,今天我们就要进入Atomic底层原理的的学习了,首先我们从AtomicInteger这个比较简单的原子类开始,在说AtomicInteger的底层原理之前呢,我先给你看两个例子:
实测样例对比Integer和AtomicInteger的线程安全性
Integer的测试样例
(1)定义一个共享变量Integer
(2)定义一个新的线程类,创建两个线程 ,每个 线程执行10000 次value++ 操作
java
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() 操作
java
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 内部有哪些属性以及作用是什么:
java
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对象内部的偏移地址的:
java
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的构造方法源码:
java
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
提供了两个构造方法 ,第一个是在创建AtomicInteger对象 的时候直接给内存存储值的volatile int value设置初始化的值 ;第二个没有赋初始值 ,那默认就是0
AtomicInteger方法的源码分析
getAndIncrement()方法源码
java
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
我们看到AtomicInteger 的getAndIncrement ()方法源码很简单,底层就是基于unsafe.getAndAddInt包装了一下 ,让我们继续看一下unsafe.getAndAddInt方法源码:
java
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源码:
java
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
底层也还是直接调用unsafe的compareAndSwapInt方法直接去修改,不过这里不同的是,只会执行一次CAS操作,即使失败了也不会重复CAS
其它方法源码:
其它的方法,基本都是直接调用unsafe.getAndInt方法,上面我们分析过了
java
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 属性
java
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的构造函数源码:
java
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,证明了上面的猜想
java
public final boolean get() {
return value != 0;
}
然后再看一下AtomicBoolean最常用最重要的方法compareAndSet源码:
java
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 的底层原理就到这里了,我们明天继续......
小陈:我们下一章见。
目录
JAVA并发专题 《筑基篇》
4.什么是MESI缓存一致性协议?怎么解决并发的可见性问题?
JAVA并发专题《练气篇》
10.synchronized底层之monitor、对象头、Mark Word?
11.synchronized底层是怎么通过monitor进行加锁的?
12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁
13.synchronized怎么保证可见性、有序性、原子性?
JAVA并发专题《结丹篇》
17.AtomicInteger、AtomicBoolean的底层原理
18.AtomicReference、AtomicStampReference底层原理
19.Atomic中的LongAdder底层原理之分段锁机制
20.Atmoic系列Strimped64分段锁底层实现源码剖析
JAVA并发专题《金丹篇》
21.AQS是个啥?为啥说它是JAVA并发工具基础框架?
22.基于AQS的互斥锁底层源码深度剖析
23.基于AQS的共享锁底层源码深度剖析
24.ReentrantLock是怎么基于AQS实现独占锁的?
25.ReentrantLock的Condition机制底层源码剖析
26.CountDownLatch 门栓底层源码和实现机制深度剖析
27.CyclicBarrier 栅栏底层源码和实现机制深度剖析
28.Semaphore 信号量底层源码和实现机深度剖析
29.ReentrantReadWriteLock 读写锁怎么表示?
- ReentrantReadWriteLock 读写锁底层源码和机制深度剖析
JAVA并发专题《元神篇》并发数据结构篇
31.CopyOnAarrayList 底层分析,怎么通过写时复制副本,提升并发性能?
32.ConcurrentLinkedQueue 底层分析,CAS 无锁化操作提升并发性能?
33.ConcurrentHashMap详解,底层怎么通过分段锁提升并发性能?
34.LinkedBlockedQueue 阻塞队列怎么通过ReentrantLock和Condition实现?
35.ArrayBlockedQueued 阻塞队列实现思路竟然和LinkedBlockedQueue一样?
36.DelayQueue 底层源码剖析,延时队列怎么实现?
37.SynchronousQueue底层原理解析
JAVA并发专题《飞升篇》线程池底层深度剖析
- 什么是线程池?看看JDK提供了哪些默认的线程池?底层竟然都是基于ThreadPoolExecutor的?
39.ThreadPoolExecutor 构造函数有哪些参数?这些参数分别表示什么意思?
40.内部有哪些变量,怎么表示线程池状态和线程数,看看道格.李大神是怎么设计的?
-
ThreadPoolExecutor execute执行流程?怎么进行任务提交的?addWorker方法干了啥?什么是workder?
-
ThreadPoolExecutor execute执行流程?何时将任务提交到阻塞队列? 阻塞队列满会发生什么?
-
ThreadPoolExecutor 中的Worker是如何执行提交到线程池的任务的?多余Worker怎么在超出空闲时间后被干掉的?
-
ThreadPoolExecutor shutdown、shutdownNow内部核心流程
-
再回头看看为啥不推荐Executors提供几种线程池?
-
ThreadPoolExecutor线程池篇总结