并发编程之并发内存模型——JMM与内存屏障

目录

JMM

volatile

查看Java底层的汇编语言(了解)

 有序性

双重检查锁

内存屏障


计算机多核并发缓存架构:磁盘—>主内存(RAM)—〉CPU高速缓存—>CPU寄存器

并发编程之并发内存模型——JMM与内存屏障_第1张图片

JMM

JMM: java多线程的内存模型

Java多线程内存模型与CPU缓存模型类似,是基于cpu缓存模型来建立的,Java线程内存模型是标准化的,屏蔽掉了底层不同计算机的区别。

并发编程之并发内存模型——JMM与内存屏障_第2张图片

示例代码: 

package com.xxqg.mpdc.common;

/**
 * @description
 * @author: ZhaoYue
 * @create: 2021-07-21 10:59
 **/
public class TestClass {
    //共享变量
    private static Boolean initFlag = false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            System.out.println("waiting data...");
            //多线程同时操作一个共享变量时,每个线程操作的都是各自共享变量的副本
            //感知不到别的线程对此变量的修改
            while(!initFlag){

            }
            System.out.println("=======================success");

        }).start();
        Thread.sleep(2000);
        new Thread(() -> prepareData()).start();
    }
    public static void prepareData(){
        System.out.println("prepare data...");
        //每个线程操作的都是共享变量的副本
        initFlag = true;
        System.out.println("prepare data end");
    }
}

 运行结果:

 想要达到预期的执行效果,解决方式:在共享变量initFlag前加volatile即可。

运行结果:

volatile

 volatile:保证多线程之间共享变量的可见性。 

内存模型底层八大原子操作:(JDK的产品需求)

并发编程之并发内存模型——JMM与内存屏障_第3张图片 并发编程之并发内存模型——JMM与内存屏障_第4张图片

线程修改共享变量之后,什么时候同步回主内存不一定。最迟在线程结束之前。

线程之间不能直接同步数据,需要主内存做中转。 

并发编程之并发内存模型——JMM与内存屏障_第5张图片

并发编程之并发内存模型——JMM与内存屏障_第6张图片

查看Java底层的汇编语言(了解)

查看Java底层的汇编语言实现需要下载一个包:查看运行代码的汇编指令.zip(笔记中的名称)

解压之后:

并发编程之并发内存模型——JMM与内存屏障_第7张图片

将这两个文件放在jdk/jre/bin目录下 。

在运行程序之前,执行以下操作:添加VM的参数以及修改刚才放入文件的JRE:

并发编程之并发内存模型——JMM与内存屏障_第8张图片并发编程之并发内存模型——JMM与内存屏障_第9张图片

运行程序之后发现在加了volatile之后,底层汇编语言在操作共享变量时加了lock前缀

汇编代码能看懂就慢慢看吧!

并发编程之并发内存模型——JMM与内存屏障_第10张图片

 有序性

指令重排序

代码示例

/**
 * @description volatile测试-有序性
 * @author: ZhaoYue
 * @create: 2021-07-21 10:59
 **/
public class TestClass {
    static int x = 0 , y = 0;
    static int a = 0 , b = 0;
    public static void main(String[] args) throws InterruptedException {
        Set resultSet = new HashSet<>();
        for(int i = 0 ; i < 100000 ; i ++){
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            Thread one = new Thread(() -> {
                //cpu会对以下两个代码做一个指令重排序
                a = y; //3
                x = 1; //1

            });
            Thread two = new Thread(() -> {
                b = x; //4
                y = 1; //2
            });
            one.start();
            two.start();
            one.join();
            two.join();

            //运行结果可能会出现 a=1 b=1可情况
            resultSet.add("a=" + a + "," + "b = " + b);
            System.out.println(resultSet);
        }
    }
}

运行结果

并发编程之并发内存模型——JMM与内存屏障_第11张图片

并发编程运行结果很容易出乎意料,导致代码出现bug,所以开发时要慎重。

并发相关知识点:并发基础(AQS、CAS、线程间通讯、ThreadLocal)、并发集合、线程池、并发工具类(闭锁)、内存模型 、锁、原子操作、线程通讯等。

as-if-serial的语义分析判断是否依赖只在单线程里,多线程管不了。 

happens-before原则是说哪些情况不能重排序

双重检查锁

/**
 * @description 单例测试-阿里双重检测锁DCL对象半初始化问题
 * @author: ZhaoYue
 * @create: 2021-07-21 10:59
 **/
public class TestClass {
    // 如果不加volatile 此代码在并发场景下会有隐患
    private static volatile TestClass testClass = null;
    // 构造私有化
    private TestClass(){}

    public static TestClass getInstance(){
        // 一重检测 目的是判断是否需要初始化
        if(testClass == null){
            // 加锁
            synchronized (TestClass.class){
                // 两重检测 目的是为了判断在等锁的过程中有没有被别的线程初始化
                if(testClass == null){
                    testClass = new TestClass();
                }
            }
        }
        return testClass;
    }
    public static void main(String[] args) throws InterruptedException {
        TestClass testClass = TestClass.getInstance();
    }
}

如果想看出隐患问题需要看它的字节码文件,操作如下(需要提前安装一个可以打印方法的字节码文件的插件jclasslib)

并发编程之并发内存模型——JMM与内存屏障_第12张图片

 synchronized代码块的字节码

并发编程之并发内存模型——JMM与内存屏障_第13张图片

对象的创建过程 (new)

并发编程之并发内存模型——JMM与内存屏障_第14张图片

以对象中int属性为例:先赋初值0,在执行底层的init方法赋程序员设置的默认值,最后执行构造方法赋值。 

字节码中的第11行(init 赋值null)和12行(赋值new TestClass()的值),是没有依赖关系的,不违背as-if-serial语义和happens-before原则,所以说CPU是有可能对这两行代码进行指令重排序的。如果重排序了,就会先赋new TestClass()的值,此时对象就不为null了,那么别的线程就可以使用了。但这个时候还没有初始化完。即成员变量还没有赋真正的值,构造方法也还没有调用。就是对象的半初始化。解决方法是在成员变量上面加上volatile关键字。加了volatile之后就不会重排序了,原因如下:

内存屏障

load是读操作,store是写操作。 

并发编程之并发内存模型——JMM与内存屏障_第15张图片

volatile中有lock前缀指令,相当于给这个变量前后加了内存屏障的功能,那么这个变量在赋值的过程中就不能重排序。

你可能感兴趣的:(笔记,java)