1 简介

  Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立的,我们逐个来看一下哪些操作实现了这三个特性
 
2 原子性
2.1 操作指令

  由Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和write这六个,我们大致可以认为,基本数据类型的访问、读写都是具备原子性的(例外就是long和double的非原子性协定,知道这件事情就可以了,无须太过在意这些几乎不会发生的例外情况)。
 
2.2 synchronized
  如果应用场景需要一个更大范围的原子性保证(经常会遇到),Java内存模型还提供了lock和unlock操作来满足这种需求,尽管虚拟机未把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操作。这两个字节码指令反映到Java代码中就是同步块——synchronized关键字,因此在synchronized块之间的操作也具备原子性
 
3 可见性
3.1 volatile

  可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。volatile变量做到了这一点。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此。普通变量与volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点
 
3.2 synchronized
  同步块的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)”这条规则获得的。
 
3.3 final
  final关键字的可见性是指:被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那么在其他线程中就能看见final字段的值。
 
4 有序性

  Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指“线程内似表现为串行的语义”(Within-Thread As-If-SerialSemantics),后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象
 
4.1 volatile

  volatile关键字本身就包含了禁止指令重排序的语义
 
4.2 synchronized

  synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入
 
5 先发性原则
5.1 简介

  如果Java内存模型中所有的有序性都仅靠volatile和synchronized来完成,那么有很多操作都将会变得非常啰嗦,但是我们在编写Java并发代码的时候并没有察觉到这一点,这是因为Java语言中有一个“先行发生”(Happens-Before)的原则。这个原则非常重要,它是判断数据是否存在竞争,线程是否安全的非常有用的手段。
 
5.2 先发性的8条规则
5.2.1 程序次序规则(Program Order Rule)

  在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。注意,这里说的是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构
 
5.2.2 管程锁定规则(Monitor Lock Rule)

  一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是“同一个锁”,而“后面”是指时间上的先后
 
5.2.3 volatile变量规则(Volatile Variable Rule)
  对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后
 
5.2.4 线程启动规则(Thread Start Rule)

  Thread对象的start()方法先行发生于此线程的每一个动作
 
5.2.5 线程终止规则(Thread Termination Rule)
  线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行
 
5.2.6 线程中断规则(Thread Interruption Rule)

  对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread::interrupted()方法检测到是否有中断发生
 
5.2.7 对象终结规则(Finalizer Rule)

  一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始
 
5.2.8 传递性(Transitivity)
  如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论
 
5.3 示例
private int value = 0;
pubilc void setValue(int value) {
this.value = value;
} public int getValue() {
return value;
}
  假设存在线程A和B,线程A先(时间上的先后)调用了setValue(1),然后线程B调用了同一个对象的getValue(),那么线程B收到的返回值是什么

  我们依次分析一下先行发生原则中的各项规则。由于两个方法分别由线程A和B调用,不在一个线程中,所以程序次序规则在这里不适用;由于没有同步块,自然就不会发生lock和unlock操作,所以管程锁定规则不适用;由于value变量没有被volatile关键字修饰,所以volatile变量规则不适用;后面的线程启动、终止、中断规则和对象终结规则也和这里完全没有关系。因为没有一个适用的先行发生规则,所以最后一条传递性也无从谈起,因此我们可以判定,尽管线程A在操作时间上先于线程B,但是无法确定线程B中getValue()方法的返回结果,换句话说,这里面的操作不是线程安全的

  把getter/setter方法都定义为synchronized方法,这样就可以套用管程锁定规则;要么把value定义为volatile变量,由于setter方法对value的修改不依赖value的原值,满足volatile关键字使用场景,这样就可以套用volatile变量规则来实现先行发生关系

JAVA虚拟机22-原子性、可见性与有序性、先行发生原则的更多相关文章

  1. Java高并发--原子性可见性有序性

    Java高并发--原子性可见性有序性 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 原子性:指一个操作不可中断,一个线程一旦开始,直到执行完成都不会被其他线程干扰.换 ...

  2. Java之先行发生原则与volatile关键字详解

    volatile关键字可以说是Java虚拟机提供的最轻量级的同步机制,但是它并不容易完全被正确.完整地理解,以至于许多程序员都习惯不去使用它,遇到需要处理多线程数据竞争问题的时候一律使用synchro ...

  3. java内存模型—先行发生原则

    Java语言中有一个“先行发生”(happens-before)的原则.这个原则非常重要,它是判断数据是否存在竞争,线程是否安全的主要依据,依赖这个原则,我们可以通过几条规则一揽子解决并发环境下两个操 ...

  4. JAVA多线程之先行发生原则

    一.引子 如果java内存模型中所有的有序性都仅仅依靠volatile和synchronized来完成,那么有一些操作会变得很繁琐,但我们在编写java并发代码时并未感觉到这一点,这是因为java语言 ...

  5. 先行发生原则(Happens-before)

    先行发生原则(Happens-Before)是判断数据是否存在竞争.线程是否安全的主要依据. 先行发生是Java内存,模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,那么操作A产生的影 ...

  6. Happens-before先行发生原则

    简介 从JDK1.5,java使用新的JSR-133内存模型:JSR-133使用happens-before的概念来阐述操作之间的内存可见性:在JMM中,如果一个操作执行的结果需要对另一个操作可见,那 ...

  7. 《深入理解Java虚拟机:JVM高级特性与最佳实践》【PDF】下载

    <深入理解Java虚拟机:JVM高级特性与最佳实践>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062566 内容简介 作为一位 ...

  8. 深入理解 Java 虚拟机之学习笔记(1)

    本书结构: 从宏观的角度介绍了整个Java技术体系.Java和JVM的发展历程.模块化,以及JDK的编译 讲解了JVM的自动内存管理,包括虚拟机内存区域的划分原理以及各种内存溢出异常产生的原因 分析了 ...

  9. JAVA好书之《深入理解Java虚拟机》

    最近打算做好现有工作的前提下,扎实一下自己专业的技术知识,并将相关的经典书也记录一下.今天看了一些JVM相关的知识,这里面的经典是<深入理解Java虚拟机>,适合有点基础又想深入理解其中原 ...

  10. java并发特性:原子性、可见性、有序性

    要想并发程序正确地执行,必须要保证原子性.可见性以及有序性.只要有一个没有被保证,就有可能会导致程序运行不正确. 1.原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后 ...

随机推荐

  1. Git 实战代码分支管理 | Git Flow 策略

    ​ 简介 在团队协作开发中,版本管理工具尤为重要,它可以帮助团队很好地进行代码的共享.回滚等操作,比较流行的版本管理工具有:CVS.SVN.Git.Git作为分布式版本管理工具,优势十分明显,它可以为 ...

  2. 【云原生 · Kubernetes】部署Kubernetes集群

    [云原生 · Kubernetes]搭建Harbor仓库 接着上次的内容,后续来了! 在master节点执行脚本k8s_master_install.sh即可完成K8S集群的部署,具体步骤参考如下(1 ...

  3. 内核常用的might_sleep函数

    1. 前言 内核版本:linux 4.9.225.内核版本:linux 4.9.225.对于内核常用的might_sleep函数,如果没有调试的需要(没有定义CONFIG_DEBUG_ATOMIC_S ...

  4. from 表单非空验证以及多表单提交

    开发中我们常用到$('#formid').serialize()方法进行表单序列化提交,但也相应催生了表单的非空严重以及多表单提交. form html: <form id="form ...

  5. Visual Studio高版本 ArcObject for .Net 低版本

    在基于ArcGIS的开发中,经常会存在Visual Studio版本高,ArcObject for .Net 版本低的问题.例如Visual Studio 2015的环境下,安装ArcObject f ...

  6. 【每日一题】【双指针、位运算】2022年2月3日-NC103 反转字符串

    描述 写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串.(字符串长度不超过1000) 答案:双指针 import java.util.*; public class Solution { ...

  7. CBV如何添加装饰器?

    目录 一:CBV如何添加装饰器 1.CBV中django不建议直接给类的方法加装饰器 2.CBC添加装饰器的三种方法 3.CBV添加装饰器实战 一:CBV如何添加装饰器 1.CBV中django不建议 ...

  8. 静态文件配置,django连接MySQL,ORM基本操作

    静态文件 什么是静态文件 静态文件是不怎么经常变化的文件,主要针对html文件所使用的到的各种资源. css文件.js文件.img文件.第三方框架文件 django针对静态文件资源需要单独开始一个目录 ...

  9. MySQL中这14个牛逼的功能,惊艳到我了!!!

    前言 我最近几年用MYSQL数据库挺多的,发现了一些非常有用的小玩意,今天拿出来分享到大家,希望对你会有所帮助. 1.group_concat 在我们平常的工作中,使用group by进行分组的场景, ...

  10. MyBatis四大参数两种写法

    MyBatis四大参数两种写法 1.在主配置文件中,直接写到value值里面 2.四大参数写入单独配置文件 开始是 为了避免以后其他框架冲突 都加个前缀 jdbc. 主要是红色框里的三个部分 自己定义 ...