JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来建立的。因此,我们首先必须了解这些概念

1,原子性

原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰,比如,对于一个静态全局变量int i,两个线程同时对它赋值,线程A给他赋值1,线程B给它赋值为-1。那么不管这2个线程以何种方式、何种步调工作,i的值要么是1,要么是-1。线程A和线程B之间是没有干扰的。这就是原子性的一个特点,不可被中断

2,可见性(Visibility)

可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。显然,对于串行程序来说,可见性问题是不存在的。因为你在任何一个操作步骤中修改了某个变量,那么在后续的步骤中,读取这个变量的值,一定是修改后的新值。

3 有序性(Ordering)

有序性问题可能是三个问题中最难理解的了。对于一个线程的执行代码而言,我们总是习惯地认为代码的执行是从先往后,依次执行的。这么理解也不能说完全错误,因为就一个线程内而言,确实会表现成这样。但是,在并发时,程序的执行可能就会出现乱序。给人直观的感觉就是:写在前面的代码,会在后面执行。听起来有些不可思议,是吗?有序性问题的原因是因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致

                                                                                                                   以上概念均摘自《实战java高并发程序设计》

Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。

主内存和工作内存

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享。

Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(可以与前面讲的处理器的高速缓存类比),线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程、主内存和工作内存的交互关系如下图所示,和上图很类似。

注意:这里的主内存、工作内存与Java内存区域的Java堆、栈、方法区不是同一层次内存划分,这两者基本上没有关系。

内存交互操作

由上面的交互关系可知,关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:

  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。Java内存模型只要求上述两个操作必须按顺序执行,而没有保证必须是连续执行。也就是read和load之间,store和write之间是可以插入其他指令的,如对主内存中的变量a、b进行访问时,可能的顺序是read a,read b,load b, load a。Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:

  • 不允许read和load、store和write操作之一单独出现
  • 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,lock和unlock必须成对出现
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

关于volatile

Java 使用了一些特殊的操作或者关键字来申明、告诉虚拟机,在这个地方,要尤其注意,不能随意变动优化目标指令。关键字volatile 就是其中之一。当你用volatile 去申明一个变量时,就等于告诉了虚拟机,这个变量极有可能会被某些程序或者线程修改。为了确保这个变量被修改后,应用程序范围内的所有线程都能够“看到”这个改动,虚拟机就必须采用一些特殊的手段,保证这个变量的可见性等特点。

先来看一个使用场景,如下面一段代码在32位java虚拟机上的运行

public class Test {public static long t = 0;public static class ChangeT implements Runnable {private long to;public ChangeT(long to) {this.to = to;}@Overridepublic void run() {while (true) {Test.t = to;Thread.yield();}}}public static class ReadT implements Runnable {@Overridepublic void run() {while (true) {long temp = Test.t;if (temp != 111L && temp != -999L && temp != 333L && temp != -444L) {System.out.println(temp);}Thread.yield();}}}public static void main(String args[]) {new Thread(new ChangeT(111L)).start();new Thread(new ChangeT(-999L)).start();new Thread(new ChangeT(333L)).start();new Thread(new ChangeT(-444L)).start();new Thread(new ReadT()).start();}
}

可以看出由于long是64位在32位的虚拟机上通过多线程赋值,导致数据的读和写都不是原子性的,因而造成,打印出的数字部分是乱的

-4294966963
4294966297
-4294966963
-4294966963
4294966297
-4294967185
-4294966963
4294966297
...

但当使用 volatile修饰后就没有问题了

public volatile static long t = 0;

同时也需要注意volatile是无法完全保证一些复合操作的原子性,它并不能够代替锁。

package com.ishop.controller;public class Test {public volatile static int i = 0;public static void main(String[] args) throws InterruptedException {AddThread t1 = new AddThread();AddThread t2 = new AddThread();AddThread t3 = new AddThread();AddThread t4 = new AddThread();t1.start();t2.start();t3.start();t4.start();System.out.println("i="+i);}
}
class AddThread extends Thread {@Overridepublic void run() {for (int a = 0; a < 100000; a++){Test.i++;}}
}

如:这一段代码,输出i总是一个小于100000的值。

对于volatile的特点以及使用场景总结如下:

volatile变量具有2种特性:

  • 保证变量的可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入,这个新值对于其他线程来说是立即可见的。
  • 屏蔽指令重排序:指令重排序是编译器和处理器为了高效对程序进行优化的手段,下文有详细的分析。

volatile语义并不能保证变量的原子性。对任意单个volatile变量的读/写具有原子性,但类似于i++、i–这种复合操作不具有原子性,因为自增运算包括读取i的值、i值增加1、重新赋值3步操作,并不具备原子性。

由于volatile只能保证变量的可见性和屏蔽指令重排序,只有满足下面2条规则时,才能使用volatile来保证并发安全,否则就需要加锁(使用synchronized、lock或者java.util.concurrent中的Atomic原子类)来保证并发中的原子性。

  • 运算结果不存在数据依赖(重排序的数据依赖性),或者只有单一的线程修改变量的值(重排序的as-if-serial语义)
  • 变量不需要与其他的状态变量共同参与不变约束

因为需要在本地代码中插入许多内存屏蔽指令在屏蔽特定条件下的重排序,volatile变量的写操作与读操作相比慢一些,但是其性能开销比锁低很多。

参考:《实战java高并发程序设计》

文章部分内容引自:http://blog.csdn.net/u011080472/article/details/51337422

转载于:https://www.cnblogs.com/china2k/p/8005770.html

java并发-内存模型与volatile相关推荐

  1. 掌握Java的内存模型,你就是解决并发问题最靓的仔

    摘要:如果编写的并发程序出现问题时,很难通过调试来解决相应的问题,此时,需要一行行的检查代码,这个时候,如果充分理解并掌握了Java的内存模型,你就能够很快分析并定位出问题所在. 本文分享自华为云社区 ...

  2. 深入理解并发内存模型||JMM与内存屏障||多核并发缓存架构 ||JMM内存模型||volatile 关键字的作用 ||JMM 数据原子操作||JMM缓存不一致的问题

    深入理解并发内存模型||JMM与内存屏障 多核并发缓存架构 JMM内存模型 volatile 关键字的作用 JMM 数据原子操作 JMM缓存不一致的问题

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

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

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

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

  5. 【高并发】别闹了,这样理解Java的内存模型才正确(八种操作+同步规则)

    大家好,我是冰河~~ 最近冰河不是又出版了一本<深入理解高并发编程:核心原理与案例实战>一书吗?很多小伙伴对于Java的内存模型还是不太了解,今天,我就用最简短的篇幅结合八种操作和同步规则 ...

  6. Java的内存模型,java初级面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...

  7. 全网最硬核 Java 新内存模型解析与实验 - 2. 原子访问与字分裂

    个人创作公约:本人声明创作的所有文章皆为自己原创,如果有参考任何文章的地方,会标注出来,如果有疏漏,欢迎大家批判.如果大家发现网上有抄袭本文章的,欢迎举报,并且积极向这个 github 仓库 提交 i ...

  8. 全网最硬核 Java 新内存模型解析与实验 - 1. 什么是 Java 内存模型

    个人创作公约:本人声明创作的所有文章皆为自己原创,如果有参考任何文章的地方,会标注出来,如果有疏漏,欢迎大家批判.如果大家发现网上有抄袭本文章的,欢迎举报,并且积极向这个 github 仓库 提交 i ...

  9. 全网最硬核 Java 新内存模型解析与实验 - 4. Java 新内存访问方式与实验

    个人创作公约:本人声明创作的所有文章皆为自己原创,如果有参考任何文章的地方,会标注出来,如果有疏漏,欢迎大家批判.如果大家发现网上有抄袭本文章的,欢迎举报,并且积极向这个 github 仓库 提交 i ...

最新文章

  1. 为什么要选择useState而不是useReducer
  2. 如何去除使用inline-block之后的间距(二)?
  3. LEB(Leading edge blanking)是什么
  4. nexus5 刷原装android,nexus5 刷回原生系统
  5. 今天升级win10.vs调试程序各种崩溃
  6. 那些程序员身上共有的属性,这就是他为什么比你进步快的原因!
  7. 使用bypy从ubuntu通过命令行上传文件到百度云
  8. 四种launchMode启动方式
  9. Magpie: 基于React的炫酷的滚动抽奖程序
  10. 如何在Mac系统中修改设备名称
  11. 浏览器之硬件加速机制
  12. 面试题:socket的流程
  13. Qmail加装自动杀毒(转)
  14. 在PCB板上调试104(0.1μF)独石电容驱动MAXIM MAX3232串口芯片的心得
  15. CS5212替代RTD2166|低BOM成本替代RTD2166
  16. win7上怎么安装linux系统安装教程,怎么装系统双系统安装教程 win7 linux
  17. csharp基础练习题:反恐精英系列【难度:1级】--景越C#经典编程题库,不同难度C#练习题,适合自学C#的新手进阶训练
  18. 备份iPhone的照片和资料
  19. android开发过程中遇到的问题
  20. 谷贱伤农,薪贱伤码农!

热门文章

  1. linux 命令行看图片,骚操作:用终端打开图片
  2. mysql数据库权威指南_MySQL_MySQL权威指南读书笔记(三),第二章:MYSQL数据库里面的数 - phpStudy...
  3. 一篇搞懂HTTP协议
  4. c语言各个英文的作用,C语言最重要的知识点复习资料(国外英文资料).doc
  5. 2019哈佛计算机专业录取,2019哈佛大学早申请录取数据公布 录取率再降1个点仅为13.4%...
  6. 磁力mysql搜索_多功能搜索 搜索系统安装 小说 电影 磁力
  7. 基于javaSpringboot+mybatis+layui的装修验收管理系统设计和实现
  8. HTML+CSS+JS实现 ❤️酷炫彩虹旋转隧道特效❤️
  9. android寻找手机,寻找那些“干净”的Android手机
  10. 服务禁止方法_Linux禁止ping以及开启ping的方法