java JUC并发编程 第五章 volatile与JMM

系列文章目录

第一章 java JUC并发编程 Future: link
第二章 java JUC并发编程 多线程锁: link
第三章 java JUC并发编程 中断机制: link
第四章 java JUC并发编程 java内存模型JMM: link
第五章 java JUC并发编程 volatile与JMM: link
第六章 java JUC并发编程 CAS: link
第七章 java JUC并发编程 原子操作类增强: link
第八章 java JUC并发编程 ThreadLocal: link
第九章 java JUC并发编程 对象内存布局与对象头: link
第十章 java JUC并发编程 Synchronized与锁升级: link
第十一章 java JUC并发编程 AbstractQueuedSynchronizer之AQS: link



1 volatile 2大特点

特点,可见性,有序性,没有原子性。排序要求禁止指令重排
内存语意
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。
当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,重新回到主内存中读取最新共享变量
所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接重主内存中读取
volatile靠内存屏障Memory Barrier保证可见性和有序性。

2 volatile 内存屏障

在这里插入图片描述
在这里插入图片描述

2.1 内存屏障的分类

2.1.1粗分2种

1.读屏障(Load Barrier):在读指令之前插入读屏障,让工作内存或CPU高速缓存当中的缓存数据失效,重新回到主内存中获取最新数据
2.写屏障(Store Barrier):在写指令之后插入写屏障,强制把写缓冲区的数据刷回到主内存中

2.1.2 细分4种

loadload(),storestore(),loadstore(),storeload()
在这里插入图片描述

C++源码分析
Unsafe.class
在这里插入图片描述
Unsafe.cpp
在这里插入图片描述
orderAccess.hpp
在这里插入图片描述
orderAccess_linux_x86.inline.hpp
在这里插入图片描述

2.2.3 什么叫保证有序性?

禁重排,通过内存屏障禁重排
1.重排序有可能影响程序的执行和实现,因此,我们有时候希望告诉JVM不能重排序,
2.对于编译器的重排序,JMM会根据重排序的规则,禁止特定类型的编译器重排序。
3.对于处理器的重排序,Java变意思在生成指令序列的适当位置,插入内存屏障指令,来禁止特定类型的处理器排序

2.2.4 happens-before之volatile变量规则

在这里插入图片描述

2.2.5 lolatile读插入内存屏障后生成的指定序列示意图

在这里插入图片描述

2.2.6 volatile写插入内存屏障后生成的指令序列示意图

在这里插入图片描述

3 volatile特性

3.1 保证可见性

保证不通线程对摸个变量完成操作后结果及时可见,即改共享变量一旦改变所有线程立即可见

3.1.1 不加volatile,没有可见性,程序无法停止

package com.atguigu.springcloud.util.interrup;

import java.util.concurrent.TimeUnit;

public class VolatileSeeDemo {
    static  boolean flag = true;
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"----come in");
            while(flag){
            }
            System.out.println(Thread.currentThread().getName()+"----flag 为false");
        },"t1").start();
        try {TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e) {e.printStackTrace();}
        flag=false;
        System.out.println(Thread.currentThread().getName()+"修改完成");
    }
}

执行结果:程序无法完成
在这里插入图片描述

3.1.2 加了volatile,保证可见性,程序可以停止

package com.atguigu.springcloud.util.interrup;

import java.util.concurrent.TimeUnit;

public class VolatileSeeDemo {
//    static  boolean flag = true;
    static volatile  boolean flag = true;
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"----come in");
            while(flag){
            }
            System.out.println(Thread.currentThread().getName()+"----flag 为false");
        },"t1").start();
        try {TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e) {e.printStackTrace();}
        flag=false;
        System.out.println(Thread.currentThread().getName()+"修改完成");
    }
}

执行结果:程序正常结束
在这里插入图片描述
总结:
线程t1为何看不到被主线程main修改为false的flag的值?
1.主线程修改了flag之后没有将其刷新到主内存,所以t1线程看不到
2.主线程将flag刷新到了主内存,但是t1一直读取的是自己工作内存中的flag的值,没有取主内存中更新获取flag最新的值
诉求:
1.线程中修改了自己工作内存中的副本之后,立即将其刷新到主内存。
2.工作内存中每次读取共享变量时,都去主内存中重新读取,然后拷贝到工作内存。
解决:
使用volatile修饰共享变量,就可以达到上面的效果,被volatile修改的变量有以下特点
1.线程中读取的时候,每次读取都会去主内存中读取共享变量最新的值,然后将其复制到工作内存
2.线程中修改了工作内存中变量的副本,修改之后会立即刷新到主内存。

3.1.3 volatile变量的读写过程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2 没有原子性

3.2.1 加了synchronized关键字保证在多线程环境下的原子性

package com.atguigu.springcloud.util.interrup;

import java.util.concurrent.TimeUnit;

class  MyNumber{
    int number;
    public synchronized void addPlusPlus(){
        number++;
    }
}
public class VolatileNoAtomicDemo {
    public static void main(String[] args) {
        MyNumber myNumber = new MyNumber();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    myNumber.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        try {TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e) {e.printStackTrace();}
        System.out.println(myNumber.number);
    }
}

3.2.2 volatile不具备原子性

package com.atguigu.springcloud.util.interrup;

import java.util.concurrent.TimeUnit;

class  MyNumber{
    volatile int number;
    public void addPlusPlus(){
        number++;
    }
}
public class VolatileNoAtomicDemo {
    public static void main(String[] args) {
        MyNumber myNumber = new MyNumber();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    myNumber.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        try {TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e) {e.printStackTrace();}
        System.out.println(myNumber.number);
    }
}

多次运行后其中一次结果与预期不一样
在这里插入图片描述
为什么没有原子性?
读取赋值一个普通变量的情况
在这里插入图片描述
i++在java中的操作分为3步1.数据加载,2数据计算,3数据赋值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3 指令禁重排

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.4 正确使用volatile

单一赋值可以(volatile int a =10; bolatile boolean flag=false),但是含复合运算赋值不可以(i++之类)
状态标志,判断业务是否结束
在这里插入图片描述

开销较低的读,写锁策略
在这里插入图片描述

DCL双端锁的发布

在这里插入图片描述
在这里插入图片描述
存在指令重排序
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4 总结

4.1 volatile可见性

在这里插入图片描述

4.2 volatile没有原子性

4.3 volatile禁重排

4.3.1 写指令

在这里插入图片描述
在这里插入图片描述

4.3.2 读指令

在这里插入图片描述
在这里插入图片描述

4.4 java写了一个volatile关键字系统底层加入内存屏障,两者关系的关系

在这里插入图片描述

4.5 内存屏障是什么

内存屏障:是一种 屏障指令,它使得CPU或编译器对屏障指令的前和后所发出的内存操作,执行一个排序的约束。也叫内存栅栏或栅栏指令

4.6内存屏障能干嘛

阻止屏障两边的指令重排序
写数据时加入屏障,强制将线程私有工作内存的数据刷回主物理内存
读数据时加入屏障,线程私有工作内存的数据失效,重新到主物理内存中获取最新数据

4.7内存屏障四大指令

在每一个volatile写操作前面插入一个StoreStore屏障
在每一个volatile写操作后面插入一个StoreLoad屏障
在每一个volatile读操作后面插入一个LoadLoad屏障
在每一个volatile读操作后面插入一个LoadStore屏障

4.8 3句话总结

volatile写之前的操作,都禁止重排序到volatile之后
volatile读之后的操作,都禁止重排序到volatile之前
volatile写之后volatile读,禁止重排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值