理解JMM和Volatile

简介: 理解JMM和Volatile

什么是Volatile?

volitile是Java虚拟机提供轻量级的同步机制,它有以下三个重要概念

  1. 保证可见性(和JMM挂钩)
  2. 不保证原子性
  3. 禁止指令重排

什么是JMM?

JMM:Java内存模型,是一个不存在的东西,它是一个概念,是一个约定

关于JMM的一些同步的约定

1 线程解锁前 必须把共享变量刷回主存

2 线程加锁前 必须读取内存中的最新值到工作内存中

3 必须保证加锁和解锁是同一把锁

线程分为工作内容和主内存

JMM有八种内存交互操作

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

    lock   (锁定):作用于主内存的变量,把一个变量标识为线程独占状态

unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定

read  (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用

load   (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中

use   (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令

assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中

store  (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用

write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须     write

 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存

不允许一个线程将没有assign的数据从工作内存同步回主内存

一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作

一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁

如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值

如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量

对一个变量进行unlock操作之前,必须把此变量同步回主内存

代码举例

package com.wyh.volatileDemo;
import java.util.concurrent.TimeUnit;
/**
 * @program: JUC
 * @description:JMMD和Volatile
 * @author: 魏一鹤
 * @createDate: 2022-03-09 23:44
 **/
public class volatileDemo01 {
private static int num=0;
//主线程 main线程
    public static void main(String[] args) throws InterruptedException {
//再开启一条线程
        new Thread(()->{
//一直循环
            while (num==0) {
            }
        }).start();
//休眠1s
        TimeUnit.SECONDS.sleep(1);
//重新赋值num
        num=1;
//打印num
        System.out.println(num); //1
    }
}

发现问题,变量修改但是没有通知程序,导致一直执行,这个时候就需要使用Volatile进行同步了

  1. 使用volatile验证问题
  2. 保证可见性

给变量增加volatile变量修饰

package com.wyh.volatileDemo;
import java.util.concurrent.TimeUnit;
/**
 * @program: JUC
 * @description:JMMD和Volatile
 * @author: 魏一鹤
 * @createDate: 2022-03-09 23:44
 **/
public class volatileDemo01 {
//volatile修饰变量
    //不加volatile程序就会死循环 没有可见性
    private volatile static int num=0;
//主线程 main线程
    public static void main(String[] args) throws InterruptedException {
//再开启一条线程
        new Thread(()->{
//一直循环 /死循环 对主内存的数据变化是不知道的
            //解决办法 给用到的变量使用volatile修饰.增加可见性
            while (num==0) {
            }
        }).start();
//休眠1s
        TimeUnit.SECONDS.sleep(1);
//重新赋值num
        num=1;
//打印num
        System.out.println(num); //1
    }
}

通过结果发现使用volatile修饰变量之后就增加了可见性

  1. 不保证原子性

 原子性:不可分隔 要么全部成功 要么全部失败

线程A在执行任务的时候是不能被打扰的,也不能被分隔,要么同时成功,要么同时失败

     代码测试

package com.wyh.volatileDemo;
import static java.lang.Thread.activeCount;
/**
 * @program: JUC
 * @description: 验证Volatile的不可原子性
 * @author: 魏一鹤
 * @createDate: 2022-03-10 23:31
 **/
//不保证原子性
public class volatileDemo02 {
    //volatile不会保证原子性 输出结果:19571
    private volatile static int num=0;
//只要调用一次方法就让num自增一次
    //synchronized
    public   static void add(){
num++;
    }
public  static void main(String[] args){
//20个线程同时调用add方法  每个线程调用1000次,理论上结果应该=20000
        for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
                }
            }).start();
        }
//判断线程存活 线程存活才进行操作
        while (activeCount()>2){ //main gc
            //礼让线程
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+":"+num); // 没有做原子性操作 结果为19857
        //如果解决这个原子性问题呢
        //1 把add方法使用synchronized关键字进行修饰 保证线程同一执行 结果为20000
        //2 加lock
    }
}

如何不加lock和synchroinzed,怎么样保证原子性?  使用原子类,解决原子性问题

代码如下

package com.wyh.volatileDemo;
import java.util.concurrent.atomic.AtomicInteger;
import static java.lang.Thread.activeCount;
/**
 * @program: JUC
 * @description: 验证Volatile的不可原子性
 * @author: 魏一鹤
 * @createDate: 2022-03-10 23:31
 **/
//不保证原子性
public class volatileDemo02 {
//volatile不会保证原子性 输出结果:19571
    //解决原子性就要用原子类 把int换成AtomicInteger AtomicInteger:原子类int
    private  static AtomicInteger num=new AtomicInteger();
//只要调用一次方法就让num自增一次
    //synchronized
    public   static void add(){
// num++;  //不是一个原子性操作
        num.getAndIncrement(); //= +1 并不是简单的+1 而是用的CAS(CPU的并发原理) 效率极高
    }
public  static void main(String[] args){
//20个线程同时调用add方法  每个线程调用1000次,理论上结果应该=20000
        for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
                }
            }).start();
        }
//判断线程存活 线程存活才进行操作
        while (activeCount()>2){ //main gc
            //礼让线程
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+":"+num); // 没有做原子性操作 结果为19857
        //如果解决这个原子性问题呢
        //1 把add方法使用synchronized关键字进行修饰 保证线程同一执行 结果为20000
        //2 加lock
    }
}

原子类为什么这么高级

属于CAS(CPU并发原理),效率极高,等到CAS知识点再详细说明

这些原子类的底层都直接和操作系统挂钩,直接在内存中修改值

  1. 禁止指令重排

什么是指令重排?

它是一种思想,我们写的程序,计算机并不是按照我们写的那样去执行的

源代码->编译器优化的重排->指令并行也可能重排->内存系统也会重排->执行

处理器在进行指令重排的时候,程序会考虑数据之间的依赖性

如何解决指令重排?

使用volatile可以避免指令重排.增加一个内存屏障(禁止上面指令和下面指令顺序交换,保证特定的操作的执行顺序)

指令重排在哪些场景使用的最多?

单例模式(DCL懒汉式)

目录
相关文章
|
新零售 存储 分布式计算
关于用户画像那些事,看这一文章就够了
数据科学家介绍了向量化技巧,简单的数学变化可以通过可迭代对象执行。
14467 0
|
前端开发 Java 关系型数据库
JavaWeb用户登录注册实例(mybatis、maven、mysql、tomcat、servlet)
JavaWeb用户登录注册实例(mybatis、maven、mysql、tomcat、servlet)
JavaWeb用户登录注册实例(mybatis、maven、mysql、tomcat、servlet)
|
存储 算法 搜索推荐
C++实现图 - 05 拓扑排序
今天来讲另一个非常重要的知识点 —— 拓扑排序。咋一看好像是一个排序算法,然而它和排序扯不上半点关系,它可以用于判断我们的图中是否存在有向环。
819 0
C++实现图 - 05 拓扑排序
方法:号码如何批量导入苹果手机通讯录?
操作一:在电脑上打开软件,金芝号码提取导入助手。然后你把你的号码复制过来,放进软件的“导入通讯录”功能。操作二:选择“生成通讯录”,会得到一个文件,你把文件存放到电脑桌面。操作三:最后你再把电脑桌面上的文件,通过电脑上的薇xin或电脑Q,发送给你的手机徽信或者手机扣扣,在苹果手机上打开它,即可把号码批量导入苹果手机通讯录。
方法:号码如何批量导入苹果手机通讯录?
|
Linux
什么是Linux的LVM:PE, PV, VG, LV 的意义是?
什么是Linux的LVM:PE, PV, VG, LV 的意义是?
1437 0
什么是Linux的LVM:PE, PV, VG, LV 的意义是?
|
关系型数据库 MySQL Linux
centos7 安装 Mysql 5.7.28,详细完整教程
centos7 安装 Mysql 5.7.28,详细完整教程
832 0
centos7 安装 Mysql 5.7.28,详细完整教程
|
存储 安全 编译器
CPU和寄存器详解
CPU和寄存器详解
1168 0
CPU和寄存器详解
|
图形学 Android开发
解锁套娃新技能:FairyGUI在Unity中实现List嵌套List/立体画廊等,玩出花儿来
众所周知,人生是一个漫长的流程,不断克服困难,不断反思前进的过程。在这个过程中会产生很多对于人生的质疑和思考,于是我决定将自己的思考,经验和故事全部分享出来,以此寻找共鸣!!!
2287 0
|
存储 关系型数据库 定位技术
阿里云数据库时空引擎Ganos上线,为位置智能提供核心引擎
2018年12月10日,阿里云PostgreSQL数据库时空引擎Ganos正式上线,将为各类GIS地理空间数据以及时空型数据提供云上的高效存储、查询和分析计算服务。Ganos将数据库、GIS引擎和新型时空计算整合,利用云上GPU加速、OSS大规模存储能力,为更广泛领域建立基于位置智能提供基础核心能力。
5929 0
|
Linux 虚拟化
锐速 ServerSpeeder 无限带宽破解版一键安装包
前言 锐速serverspeeder是一款TCP网络加速软件,能在Linux系统和Windows系统的服务器中安装,安装后能启到提高网络连接稳定性、带宽利用率、低访问失败率等作用,从而提高服务器网络访问速度。
5884 0