一、JMM 内存模型:

1、什么是 JMM 内存模型:

Java 内存模型是 Java 虚拟机定义的一种多线程访问 Java 内存各个变量的访问规范,主要围绕如何解决并发过程中的原子性、可见性、有序性这三个问题来解决线程的安全问题。

Java 内存模型将内存分为了主内存和工作内存(也称为栈空间)。主内存存放所有的共享变量,所有线程都可以访问。每个线程都有自己的工作内存,存储了该线程使用到的变量的副本,线程对变量的所有操作都必须在自己的工作内存中完成,不能直接操作主存中的变量。操作时,首先将变量从主内存拷贝到自己的工作内存中,然后在自己的工作内存中对变量进行操作,操作完成后再将变量写回主存。不同的线程间也无法直接访问对方的工作内存的变量,线程间的变量值的传递必须通过主内存来完成。

(1)原子性:原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。

(2)可见性:可见性指的是,当一个线程修改了某个共享变量的值,其他线程能够马上得知这个修改的值。

串行程序不存在可见性问题,因为在任何一个操作中修改了某个变量的值,后续的操作中都能读取这个修改过的变量值。但在多线程环境中可就不一定了,因为线程对共享变量的操作都是拷贝到各自的工作内存中进行操作后才写回到主内存中的,这就可能存在一个线程A修改了共享变量x的值,还未写回主内存时,另外一个线程B又对主内存中同一个共享变量x进行操作,但此时A线程工作内存中共享变量x对线程B来说并不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题,另外指令重排以及编译器优化也可能导致可见性问题

(3)有序性:对于多线程环境,因为程序编译成机器码指令后可能会出现指令重排现象,重排后的指令与原指令的顺序未必一致,有可能出现乱序现象

指令重排序:计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排,在单线程条件下,指令重排序可以保证执行结果的一致性,但是在多线程条件下,这些重排优化可能会导致程序出现内存可见性问题,不能保证多线程间语义一致性

2、原子性、可见性、有序性问题的解决措施:

(1)原子性问题:除了 JVM 自身提供对基本数据类型读写操作的原子性外,对于方法级别或者代码级别的原子性操作,可以使用 synchronized 关键字或者重入锁 ReentrantLock 保证程序执行的原子性。

(2)可见性问题:工作内存与主内存同步延迟现象导致的可见性问题,可以使用 synchronized 关键字、Lock 或者 volatile 关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见。

(3)有序性问题:可以利用 volatile、synchronized 关键字解决。

3、JMM 的 as-if-serial 规则和 happens-before 规则:

(1)as-if-serial:无论编译器和处理器如何进行重排序,单线程程序的执行结果不会改变。

(2)happens-before:在多线程程序开发中,如果仅靠 synchronized 和 volatile 关键字来保证原子性、可见性以及有序性,那么编写并发程序可能会显得十分麻烦,所以 Java 内存模型中提供了内置的 happens-before 规则来辅助处理前面提到的问题,它是判断数据是否存在竞争、线程是否安全的依据,从而保证线程安全。一个操作 happens-before 另一个操作,表示第一个的操作结果对第二个操作可见,并且第一个操作的执行顺序也在第二个操作之前。但这并不意味着 JVM 必须按照这个顺序来执行程序。如果重排序后的执行结果与按 happens-before 关系执行的结果一致,JVM 也允许重排序的发生。happens-before 原则内容如下:

  • 程序顺序原则:即在一个线程内必须保证语义串行性,也就是说按照代码顺序执行。

  • 锁规则:解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,也就是说,如果对于一个锁解锁后,再加锁,那么加锁的动作必须在解锁动作之后(同一个锁)。

  • volatile 规则:volatile 变量的写,先发生于读,这保证了volatile变量的可见性,简单的理解就是,volatile 变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能够看到该变量的最新值。

  • 线程启动规则:线程的 start() 方法先于它的每一个动作,即如果线程A在执行线程B的 start() 方法之前修改了共享变量的值,那么当线程B执行 start() 方法时线程A对共享变量的修改对线程B可见。

  • 传递性:A先于B ,B先于C,那么A必然先于C

  • 线程终止规则:线程的所有操作先于线程的终结,Thread.join() 方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程从线程B的 join() 方法成功返回后,线程B对共享变量的修改将对线程A可见。

  • 线程中断规则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 Thread.interrupted() 方法检测线程是否中断。

  • 对象终结规则:对象的构造函数执行,结束先于 finalize() 方法。

二、volatile 内存语义:

1、volatile 的作用:

volatile 是 Java 虚拟机提供的轻量级同步机制,是线程不安全的,volatile 跟可见性和有序性有关,被 volatile 修饰的共享变量,具有以下两个作用:

(1)保证不同线程对该变量操作的内存可见性:当变量被 volatile 修饰时,那么对它的修改会立刻刷新到主存,同时其他线程操作 volatile 变量时,JMM 会把该线程对应的工作内存置为无效,那么该线程就只能从主存中重新读取共享变量,保证读取到最新值

通过 synchronized 和 Lock 也能够保证可见性,线程在释放锁之前,会把共享变量值都刷回主存,但是相比于 volatile,synchronized 和 Lock 的开销都更大。

(2)禁止指令重排序

2、内存屏障:

volatile 在内存中的语义是通过内存屏障实现,即可见性和禁止重排优化。把加入 volatile 关键字的代码和未加入 volatile 关键字的代码都生成汇编代码,会发现加入 volatile 关键字的代码会多出一个内存屏障指令,它是一个 CPU 指令。内存屏障提供了以下功能:

  • 告诉编译器和处理器,重排序时不能把后面的指令重排序到内存屏障之前的位置,从而避免多线程环境下出现乱序执行现象

  • 保存某些变量的内存可见性,利用该特性实现 volatile 的内存可见性

3、volatile 的原子性:

volatile 的两点内存语义能保证可见性和有序性,但是不能保证原子性:对单个 volatile 变量的读/写具有原子性,但是对于类似 volatile++ 这样的复合操作就无能为力了,要想保证原子性,只能借助于 synchronized、Lock 或者并发包下的 atomic 的原子操作类了。

4、volatile使用场景:

(1)状态量标记:这种对变量的读写操作,标记为 volatile 可以保证修改对线程立刻可见,效率也比 synchronized、Lock 有一定的提升。

public class VolatileSafe {
​volatile boolean close;
​public void close(){close=true;}
​public void doWork(){while (!close){System.out.println("safe....");}}
}

对于 boolean 变量 close 值的修改属于原子性操作,因此可以通过使用 volatile 修饰变量 close,使用该变量对其他线程立即可见,从而达到线程安全的目的。

(2)单例模式的实现,典型的双重检查锁定(DCL):

这是一种懒汉的单例模式,使用时才创建对象,而且为了避免初始化操作的指令重排序,给 instance 加上了 volatile。指令重排序会导致,当一条线程访问的 instance 不为 null 时,但是实际上 instance 实例未必已初始化完成,也就造成了线程安全问题。

public class DoubleCheckLock {//禁止指令重排优化private volatile static DoubleCheckLock instance;
​private DoubleCheckLock(){}public static DoubleCheckLock getInstance(){
​//第一次检测if (instance==null){//同步synchronized (DoubleCheckLock.class){if (instance == null){//多线程环境下可能会出现问题的地方instance = new DoubleCheckLock();}}}return instance;}
}

单例模式的线程安全性:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法:

  • (1)饿汉式单例模式的写法:线程安全

  • (2)懒汉式单例模式的写法:非线程安全

  • (3)双检锁单例模式的写法:线程安全

推荐文章:https://blog.csdn.net/javazejian/article/details/72772461

JUC多线程:JMM内存模型与volatile内存语义相关推荐

  1. JVM内存模型 和 Java内存模型 对比学习

    前言 首先要知道这两者不是同一个东西,Jvm内存模型 也叫 Java内存区域.Java运行时数据区域 而Java内存模型 是 JMM (Java Memory Model,简称 JMM),是定义了线程 ...

  2. jvm内存模型_JVM|02内存模型

    JVM内存模型 概述 Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保 ...

  3. java ps old gen perm gen_Java 内存模型之堆内存管理

    Java 内存模型之堆内存(Heap) 一.背景知识:内存类型介绍 1.什么是 Perm Gen? Perm Gen : Permanent Generation Perm Gen 区是一个特殊的JV ...

  4. java内存模型 原子性_Java内存模型JMM 高并发原子性可见性有序性简介 多线程中篇(十)...

    JVM运行时内存结构回顾 在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下 整体结构如下图所示,大致分为五大块 而对于方法区中的数据,是属于所有线程共享的数据结构 而对于虚拟机栈中数据 ...

  5. 内存位置访问无效_万字长文——java内存模型之volatile深入解读

    在阅读本文前,请思考以下的面试题? volatile是什么? volatile的特性 volatile是如何保证可见性的? volatile是如何保证有序性的? volatile可以保证原子性吗? 使 ...

  6. Java内存模型、volatile、原子性、可见性、有序性、happens-before原则

    目录 1.硬件的效率与一致性: 缓存一致性(Cache Coherence) 2.Java内存模型 2.1主内存与工作内存 2.2内存间的交互 2.3 volatile型变量的特殊规则 2.3.1 保 ...

  7. java并发-内存模型与volatile

    JMM的关键技术点都是围绕着多线程的原子性.可见性和有序性来建立的.因此,我们首先必须了解这些概念 1,原子性 原子性是指一个操作是不可中断的.即使是在多个线程一起执行的时候,一个操作一旦开始,就不会 ...

  8. java很贵可以用before代替吗_Java内存模型与Volatile,Happen-Before原则等

    Java的内存模型 Java内存模型(JMM)是一个抽象的模型.决定了线程主要定义了线程和内存间的抽象关系:主内存存放的是线程共享变量,每个线程有自己的工作内存,存放变量的副本,只能对副本进行读写,副 ...

  9. 【你好面试官】008 Java内存模型指volatile底层原理详解、多处理器原子操作实现原理

    微信公众号:你好面试官 这里没有碎片化的知识,只有完整的知识体系. 专注于系统全面的知识点讲解,面试题目解析; 如果你觉得文章对你有帮助,欢迎关注.分享.赞赏. ###前言 二蛋几天没有收到面试通知, ...

最新文章

  1. 5G的频谱效率,到底有多高
  2. Cisco 3550配置DHCP的实际经验
  3. sendmessage和postmessage的区别
  4. 阿里巴巴微服务开源项目盘点(持续更新)
  5. matlab数字图像处理课程设计报告,数字图像处理课程设计实验报告.doc
  6. 服务器宝塔怎么开启php5.4伪静态,WeCenter在宝塔面板LNMP环境下开启伪静态方法
  7. asynchttpclient学习
  8. python画图代码大全-Python实现画图软件功能方法详解
  9. bccomp php扩展,PHP 中文工具包 ChineseUtil v2.0 发布,引入 FFI 提升性能节省内存
  10. 校园 计算机网络设置路由器,Drcom校园网连接路由器怎么设置
  11. 【双系统】删除linux系统扇区后无法启动windows
  12. centOS下,怎么能快速锁屏
  13. Eclipse MyEclipse 代码提交时,让svn忽略classpath、target、.project
  14. HashMap是什么?HashMap的底层原理是什么?
  15. 企业级360°全方位用户画像:环境搭建[五]
  16. 使用HttpClient模拟POST请求
  17. 火箭弹道设计 matlab,航天飞行动力学远程火箭弹道设计大作业 (1)
  18. 【iOS开发】-UIPickerView
  19. Linux “挂载” 的概念
  20. 试题 算法训练 九宫格的奥(解题思路与代码)

热门文章

  1. 【数据分析+建模案例】基于所给数据集对饭店流量指标进行预测
  2. SIGIR 2021 | 基于不确定性正则化与迭代网络剪枝的终身情感分类方法
  3. ACL 2021 | 结构化知识蒸馏方法
  4. 2021 CVPR论文解读征集令
  5. WWW 2021 | 融合先验知识的BERT注意力模型
  6. 为什么大家都在吹捧Python?
  7. TIP 2018论文概述:基于深度学习的HEVC复杂度优化
  8. 每周「Paper + Code」清单:句子嵌入,文本表示,图像风格转换
  9. 在pytorch中自定义dataset读取数据2021-1-8学习笔记
  10. ProjectEuler500 【组合数学】【数论】