高级并发编程系列十三(一文搞懂原子类)

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来讲杠杠的。据说目前是个面试热点话题哟!值得去研究一下

相关文章
相关标签/搜索