扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
本篇内容介绍了“高级并发编程系列之什么是原子类”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
成都创新互联公司坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都网站设计、成都网站建设、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的保靖网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
相信作为资深程序员的你,对于AtomicInteger这样的类,即以Atomic开始的类一定不会感到陌生。我们在翻看很多框架源码、或者第三方组件都会经常看到它们,如影随形。
那么问题来了,像Atomicxxx这样的类,到底是什么意思呢?从字面意思比较好理解,Atomic即原子性,那么Atomicxxx即原子类。讲到这里,你一定还记得我们说过线程安全的三个基本要素,我们一起来回顾一下:可见性、原子性、有序性。原子类的原子性,讲的就是这个原子性,于是你可以先记住一个结论:原子类,它是线程安全的类。
到这里有朋友可能会提出质疑:你说线程安全,就线程安全吗?我不服,你没有讲清楚。我不听,我不听......好吧,看官们莫急,且听我一步一步分析,娓娓道来,话说......
#考考你: 1.你真的理解原子类的核心思想吗 2.你在你的项目中,有直接用到过原子类吗
案例描述:
定义一个普通的int型变量value,初始值为:0
开启两个线程thread_1,thread_2并行执行value++操作
每个线程执行 5000次,预期执行结果: 2 * 5000 = 10000次
通过观察最终执行结果,是否等于预期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); } }
执行结果分析:
案例描述:
定义一个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); } }
执行结果分析:
通过比较普通类型int型变量自增操作,与原子型AtomicInteger型变量自增操作。我们看到应用层代码几乎没有差异,仅仅是通过AtomicInteger替换int实现自增操作,即保证了线程安全。那么AtomicInteger它是如何做到的呢?
要分析清楚AtomicInteger底层原理,还需要回到我们说过的线程安全基本要素:可见性、原子性、有序性。就是说不管通过什么手段,要实现线程安全,一定要满足这三个基本要素,换句话说,满足了三个基本要素,也即实现了线程安全。
那么我们就从这三个要素开始分析。首先看最容易理解的有序性,你还记得什么是有序性吗?它是说线程内有序,线程之间无序。有序性比较好理解,我们就不过多解释了。
再来看可见性,同样你还记得什么是可见性吗?我们知道jmm内存模型,每个线程都有自己的私人空间(工作内存),所有线程共享公共空间(主内存)。那么如果要保证某个变量在线程间的可见性,即当线程A操作该变量后,需要同步将变量值从私人空间同步到公共空间:工作内存--->主内存;同理其它线程在操作变量前,需要从公共空间将变量值同步到私人空间:主内存--->工作内存。java编程语法上给我们提供了一个关键字:volatile。用于实现可见性。你可能还需要下面这个图:
最后再来看原子性,原子性你应该还记得,我们上一篇:高级并发编程系列十二(一文搞懂cas)刚刚分享过。cas本质上是不到黄河心不死,什么意思呢?即是不释放cpu,循环操作,直到操作成功为止。我们是这么解释的,你也应该还记得对吧。而且我们还说过对于cas,它的操作原理是三个值:内存值A、期望值B、更新值C。每次操作都会比较内存值A,是否等于期望值B、如果等于则将内存值更新成值C,操作成功;如果内存值A,不等于期望值B,则操作失败,进行下一次循环操作。你可能还需要下面这个图:
好了到这里,我们可以一起来看AtomicInteger的源码了。看看是否满足我们说的可见性、原子性。进一步分析清楚AtomicInteger类线程安全的实现原理。下面我们通过截图+文字描述的方式,方便你理解。
先来看AtomicInteger类的声明,这一块对于不熟悉的朋友可能比较难看懂,我们先截图看一下。
通过类声明部分源码,我们看到线程安全的可见性,通过volatile关键字修饰value成员变量,已经有了保障。那么原子性,又是如何保障的呢?答案是通过Unsafe工具类,进行cas操作来保障的。看图:
相信通过上面的分析,你已经理解了原子类线程安全的底层实现原理,如果你理解起来稍微还有点难度,我建议你多看两遍。对于一个程序员来说,我们不应该只会用用框架,底层思想和原理才是内功。
那么关于原子类的底层分析,我们暂时放一放,下面我们一起来看一下juc包中提供的常见原子能力工具类。它们每一个的底层原理,都在上面分析过了,我就不再逐一分析了,只是简单的列举出来,如果你感兴趣的话,可以找一两个按照我上面的分析思路,自己分析一下,应该会有意想不到的惊喜!
基本原子类,代表:AtomicInteger、AtomicLong
数组原子类,代表:AtomicIntegerArray、AtomicLongArray
引用原子类,代表:AtomicReference
提供升级能力原子类,代表:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater
累加器原子类,代表:LongAdder。关于累加器,稍微多加一句:它是jdk1.8开始后新加入的小伙伴,性能比起AtomicLong来说杠杠的。
“高级并发编程系列之什么是原子类”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流