温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

高级并发编程系列之什么是原子类

发布时间:2021-10-23 15:36:55 来源:亿速云 阅读:148 作者:iii 栏目:编程语言

本篇内容介绍了“高级并发编程系列之什么是原子类”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

1.考考你

相信作为资深程序员的你,对于AtomicInteger这样的类,即以Atomic开始的类一定不会感到陌生。我们在翻看很多框架源码、或者第三方组件都会经常看到它们,如影随形。

那么问题来了,像Atomicxxx这样的类,到底是什么意思呢?从字面意思比较好理解,Atomic即原子性,那么Atomicxxx即原子类。讲到这里,你一定还记得我们说过线程安全的三个基本要素,我们一起来回顾一下:可见性、原子性、有序性。原子类的原子性,讲的就是这个原子性,于是你可以先记住一个结论:原子类,它是线程安全的类

到这里有朋友可能会提出质疑:你说线程安全,就线程安全吗?我不服,你没有讲清楚。我不听,我不听......好吧,看官们莫急,且听我一步一步分析,娓娓道来,话说......

#考考你:
1.你真的理解原子类的核心思想吗
2.你在你的项目中,有直接用到过原子类吗

2.案例

2.1.自增操作案例

2.1.1.普通变量版本

案例描述:

  • 定义一个普通的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);
    }
}

执行结果分析:

高级并发编程系列之什么是原子类

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来说杠杠的。

“高级并发编程系列之什么是原子类”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI