1.考考你
你们周末好!又到咱们一周分享的时候了。相信做为资深程序员的你,对于AtomicInteger这样的类,即以Atomic开始的类必定不会感到陌生。咱们在翻看不少框架源码、或者第三方组件都会常常看到它们,如影随形。java
那么问题来了,像Atomicxxx这样的类,究竟是什么意思呢?从字面意思比较好理解,Atomic即原子性,那么Atomicxxx即原子类。讲到这里,你必定还记得咱们说过线程安全的三个基本要素,咱们一块儿来回顾一下:可见性、原子性、有序性。原子类的原子性,讲的就是这个原子性,因而你能够先记住一个结论:原子类,它是线程安全的类。程序员
到这里有朋友可能会提出质疑:你说线程安全,就线程安全吗?我不服,你没有讲清楚。我不听,我不听......好吧,看官们莫急,且听我一步一步分析,娓娓道来,话说......面试
#考考你: 1.你真的理解原子类的核心思想吗 2.你在你的项目中,有直接用到过原子类吗
2.案例
2.1.自增操做案例
2.1.1.普通变量版本
案例描述:编程
-
定义一个普通的int型变量value,初始值为:0数组
-
开启两个线程thread_1,thread_2并行执行value++操做安全
-
每一个线程执行 5000次,预期执行结果: 2 * 5000 = 10000次bash
-
经过观察最终执行结果,是否等于预期10000次并发
-
结果不相等,说明线程不安全,缘由是:value++操做不是一个原子性操做框架
package com.anan.edu.common.newthread.atomic; /** * 普通 int变量 ++ 操做,非原子性,线程不安全 * * @author ThinkPad * @version 1.0 * @date 2020/11/29 8:27 */ public class CommonIntDemo { /** * 普通成员变量 */ private int value = 0; public void addValue(){ value++; } public static void main(String[] args) throws InterruptedException { // 1.建立CommonIntDemo对象 CommonIntDemo demo = new CommonIntDemo(); // 2.建立2两个线程,每一个线程调用方法addValue 5000次 // 预期value值结果等于:2 * 5000 = 10000 int loopEnd = 5000; Thread thread_1 = new Thread(() -> { for(int i = 0; i < loopEnd; i++){ demo.addValue(); } }, "thread_1"); Thread thread_2 = new Thread(() -> { for(int i = 0; i < loopEnd; i++){ demo.addValue(); } }, "thread_2"); // 3.启动执行线程 thread_1.start(); thread_2.start(); // 4.主线程等待子线程执行完成,打印value值 thread_1.join(); thread_2.join(); System.out.println("int型成员变量value最终结果:" + demo.value); } }
执行结果分析:工具
2.1.2.AtomicInteger版本
案例描述:
-
定义一个AtomicInteger变量value,初始值为:0
-
开启两个线程thread_1,thread_2并行执行value.incrementAndGet()操做
-
每一个线程执行 5000次,预期执行结果: 2 * 5000 = 10000次
-
经过观察最终执行结果,是否等于预期10000次
-
结果相等,说明线程安全,缘由是:原子类同时知足了可见性、与原子性
package com.anan.edu.common.newthread.atomic; import java.util.concurrent.atomic.AtomicInteger; /** * 原子类AtomicInteger,实现自增操做,线程安全 * * @author ThinkPad * @version 1.0 * @date 2020/11/29 8:27 */ public class AtomicIntegerDemo { /** * AtomicInteger成员变量 */ private AtomicInteger value = new AtomicInteger(0); public void addValue(){ value.incrementAndGet(); } public static void main(String[] args) throws InterruptedException { // 1.建立AtomicIntegerDemo对象 AtomicIntegerDemo demo = new AtomicIntegerDemo(); // 2.建立2两个线程,每一个线程调用方法addValue 5000次 // 预期value值结果等于:2 * 5000 = 10000 int loopEnd = 5000; Thread thread_1 = new Thread(() -> { for(int i = 0; i < loopEnd; i++){ demo.addValue(); } }, "thread_1"); Thread thread_2 = new Thread(() -> { for(int i = 0; i < loopEnd; i++){ demo.addValue(); } }, "thread_2"); // 3.启动执行线程 thread_1.start(); thread_2.start(); // 4.主线程等待子线程执行完成,打印value值 thread_1.join(); thread_2.join(); System.out.println("AtomicInteger型成员变量value最终结果:" + demo.value); } }
执行结果分析:
2.2.原子类底层原理分析
2.2.1.再次分析线程安全核心思想
经过比较普通类型int型变量自增操做,与原子型AtomicInteger型变量自增操做。咱们看到应用层代码几乎没有差别,仅仅是经过AtomicInteger替换int实现自增操做,即保证了线程安全。那么AtomicInteger它是如何作到的呢?
要分析清楚AtomicInteger底层原理,还须要回到咱们说过的线程安全基本要素:可见性、原子性、有序性。就是说无论经过什么手段,要实现线程安全,必定要知足这三个基本要素,换句话说,知足了三个基本要素,也即实现了线程安全。
那么咱们就从这三个要素开始分析。首先看最容易理解的有序性,你还记得什么是有序性吗?它是说线程内有序,线程之间无序。有序性比较好理解,咱们就不过多解释了。
再来看可见性,一样你还记得什么是可见性吗?咱们知道jmm内存模型,每一个线程都有本身的私人空间(工做内存),全部线程共享公共空间(主内存)。那么若是要保证某个变量在线程间的可见性,即当线程A操做该变量后,须要同步将变量值从私人空间同步到公共空间:工做内存--->主内存;同理其它线程在操做变量前,须要从公共空间将变量值同步到私人空间:主内存--->工做内存。java编程语法上给咱们提供了一个关键字:volatile。用于实现可见性。你可能还须要下面这个图:
最后再来看原子性,原子性你应该还记得,咱们上一篇:高级并发编程系列十二(一文搞懂cas)刚刚分享过。cas本质上是不到黄河心不死,什么意思呢?便是不释放cpu,循环操做,直到操做成功为止。咱们是这么解释的,你也应该还记得对吧。并且咱们还说过对于cas,它的操做原理是三个值:内存值A、指望值B、更新值C。每次操做都会比较内存值A,是否等于指望值B、若是等于则将内存值更新成值C,操做成功;若是内存值A,不等于指望值B,则操做失败,进行下一次循环操做。你可能还须要下面这个图:
好了到这里,咱们能够一块儿来看AtomicInteger的源码了。看看是否知足咱们说的可见性、原子性。进一步分析清楚AtomicInteger类线程安全的实现原理。下面咱们经过截图+文字描述的方式,方便你理解。
2.2.2.AtomicInteger类声明
先来看AtomicInteger类的声明,这一块对于不熟悉的朋友可能比较难看懂,咱们先截图看一下。
2.2.3.方法incrementAndGet分析
经过类声明部分源码,咱们看到线程安全的可见性,经过volatile关键字修饰value成员变量,已经有了保障。那么原子性,又是如何保障的呢?答案是经过Unsafe工具类,进行cas操做来保障的。看图:
2.3.juc原子类分类
相信经过上面的分析,你已经理解了原子类线程安全的底层实现原理,若是你理解起来稍微还有点难度,我建议你多看两遍。对于一个程序员来讲,咱们不该该只会用用框架,底层思想和原理才是内功。
那么关于原子类的底层分析,咱们暂时放一放,下面咱们一块儿来看一下juc包中提供的常见原子能力工具类。它们每个的底层原理,都在上面分析过了,我就再也不逐一分析了,只是简单的列举出来,若是你感兴趣的话,能够找一两个按照我上面的分析思路,本身分析一下,应该会有意想不到的惊喜!
-
基本原子类,表明:AtomicInteger、AtomicLong
-
数组原子类,表明:AtomicIntegerArray、AtomicLongArray
-
引用原子类,表明:AtomicReference<V>。关于引用原子类,稍微加一句:它能够把一个普通对象,包装成具备原子能力的对象
-
提供升级能力原子类,表明:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater
-
累加器原子类,表明:LongAdder。关于累加器,稍微多加一句:它是jdk1.8开始后新加入的小伙伴,性能比起AtomicLong来讲杠杠的。据说目前是个面试热点话题哟!值得去研究一下