Java的内存模型

Java内存模型(JMM)是一个抽象的模型。决定了线程主要定义了线程和内存间的抽象关系:主内存存放的是线程共享变量,每个线程有自己的工作内存,存放变量的副本,只能对副本进行读写,副本的变量再刷新到主内存中。具体体现为多核CPU,每核有一个高速缓存,每个核的线程对高速缓存读写,并且有共同的主存。

主内存与工作线程交互的操作有以下八种:

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

上述八种操作均是原子操作。

如上图,若A和B两个线程同时去主存读写变量C,就会存在线程安全问题(可见性和有序性)。

Happens-Before原则

在JMM中,两个线程操作之间存在happens-before关系,则前一个操作的结果对后续操作可见。即使编译器进行指令重排序的优化,如果结果和重排序前一致,也是允许的。

因为允许指令重排序,这也说明 happens-before并不代表操作的时间顺序

有如下8条规则:

1.程序次序规则:单线程内,按照程序顺序,书写在前面的操作 happens-before 于书写在后面的操作;

2.volatile变量规则:对一个变量的写操作 happens-before 于后面对这个变量的读操作(保证了volatile变量的可见型);

3.传递规则:如果 A happens-before B,而B happens-before C,则A happens-before C;

4.锁定规则:一个unLock操作happens-before 后面对同一个锁的lock操作;

5.线程启动规则:A线程调用B线程的B.start()方法和调用之前的操作 happens-before 于B线程中的任意操作;

6.线程终结规则:线程中所有的操作都 happens-before 于线程的终止检测,如B线程的操作都happens-before 于B.join()

7.线程中断规则:对线程interrupt()方法的调用 happens-before 于被中断线程的代码检测到中断事件的发生;

8.对象终结规则:一个对象的初始化完成 happens-before 他的finalize()方法的开始

Volatile关键词

java1.5之后,通过happen-before原则增强了volatile关键词volatile关键词是轻量的实现线程安全的方法,保证了volatile变量的有序性和可见性。

1.volatile保证了变量在线程间的可见性( MESI 协议

当写 volatile 变量时,JMM 会立即该线程对应的本地内存中的共享变量值刷新到主内存。

当读 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

volatile 保证内存可见性,其实是用到了 CPU 保证缓存一致性的 MESI 协议。当某线程对 volatile 变量的修改会立即回写到主存中,并且导致其他线程的缓存行失效,强制其他线程再使用变量时,需要从主存中读取。

2.volatile保证了有序性(内存屏障)

volatile变量在读写操作前后添加内存屏障,禁止了指令的重排序,保证了有序性。

内存屏障有LoadLoad屏障,StoreStore屏障,LoadStore屏障,StoreLoad屏障。比如loadload屏障,加在Load1; Load2两个原子操作之间 ,保证在Load2及后续的读操作读取之前,Load1已经读取。其他同理。

在每个volatile写入之前,插入一个StoreStore,写入之后,插入一个StoreLoad

在每个volatile读取之前,插入LoadLoad,之后插入LoadStore。

禁止重排序规则如下图。

3.volatile无法保证原子性,只能保证自身读写为原子操作。

对volatile变量的读写 相当与给读写方法加了synchronized关键词,但volatile更轻量级。

例子

最经典的例子就是单例模式的双重检查锁模式

这里instance变量需要设置为volatile类型,这样可以保证它的new操作不会被重排序。

因为这段代码可能在重排序后分为下列三步执行:

  1. 为 instance分配内存空间
  2. 将 instance指向分配的内存地址
  3. 初始化 instance

这样在执行2时,别的线程就会判断instance不为null,直接返回还未实例化完全的instance。

并且可以知道volatile修饰的instance变量的new操作并不是原子操作,否则也就不需要synchronized加锁来保证原子性了。

总结:

JMM内存模型定义了线程和内存间的抽象关系,实际的例子就是cpu核线程,高速缓存和主存间的关系;

happens-before原则的规定保证了操作间的可见性。

volatile变量保证了有序性和可见性。主要是内存屏障和MESI 协议实现的。volatile变量的读写操作为原子操作,本身其他操作(如自增)是非原子操作。

Java内存模型与Volatile,Happen-Before原则等的更多相关文章

  1. Java并发编程:JMM(Java内存模型)和volatile

    1. 并发编程的3个概念 并发编程时,要想并发程序正确地执行,必须要保证原子性.可见性和有序性.只要有一个没有被保证,就有可能会导致程序运行不正确. 1.1. 原子性 原子性:即一个或多个操作要么全部 ...

  2. Java内存模型:volatile详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt202 Java内存模型:volatile是干什么用的Volatile字段是用 ...

  3. Java内存模型与volatile关键字

    Java内存模型与volatile关键字 一).并发程序开发 并行程序的开发要涉及多线程.多任务间的协作和数据共享问题. 常用的并发控制:内部锁.重入锁.读写锁.信号量. 二).线程的特点 线程的特点 ...

  4. Java内存模型中volatile关键字的作用

    volatile作用总结: 1. 强制线程从公共内存中取得变量的值,而不是从线程的私有的本地内存中,volatile修饰的变量不具有原子性(修改一个变量的值不能同步). 2. 保证volatile修饰 ...

  5. Java内存模型以及Volatile、Synchronize关键字的疑问

    1.众所周知,java的内存模型是一个主内存,每个线程都有一个工作内存空间,那么主内存同步到工作内存是什么时候发生的呢?工作内存同步会主内存又是什么时候发生的呢? 在cpu进行线程切换时就会发生这些同 ...

  6. 【java】java内存模型(2)--volatile内存语义详解

    多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”.可见性的意思是当一个线程 ...

  7. java内存模型与volatile变量与Atomic的compareAndSet

    java分主内存和工作内存, 主内存是线程共享的, 工作内存是每个线程独有的. java对主内存的操作是通过工作内存间接完成的: 先拷贝主内存变量值到工作内存, 在工作内存操作这个变量的副本, 完成后 ...

  8. java内存模型-volatile

    volatile 的特性 当我们声明共享变量为 volatile 后,对这个变量的读/写将会很特别.理解 volatile 特性的一个好方法是:把对 volatile 变量的单个读/写,看成是使用同一 ...

  9. 深入理解Java内存模型(四)——volatile

    volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这 ...

随机推荐

  1. JavaScript之控制标签css

    控制标签css标签.style.样式='样式具体的值'如果样式出现中横线,如border-radius,将中横线去掉,中横线后面的单词首字母大写,写成borderRadius如果原来就要该样式,表示修 ...

  2. JS-练习题

    1.foo()结果 function foo() { bar.apply(null, arguments); } function bar(){ console.log(arguments); } f ...

  3. Spring的核心容器

    Spring框架的主要功能是通过其核心容器来实现的.Spring提供了2种核心容器:BeanFactory.ApplicationContext. BeanFactory BeanFactory是一个 ...

  4. [LeetCode] 198. 打家劫舍 ☆(动态规划)

    描述 你是一个专业的小偷,计划偷窃沿街的房屋.每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警. 给定一个 ...

  5. centos7 docker安装nginx

    1.查询nginx最新镜像 docker search nginx 2.下载镜像 docker pull nginx 3.创建目录 mkdir -p /software/nginx/html mkdi ...

  6. vmware下ubuntu虚拟机如何安装vmware tools

      依次点击-->虚拟机-->安装VMware Tools   窗口下方会弹出安装提示   系统会加载安装驱动至光盘 双击--VMwareTools-****.tar.gz--的压缩包   ...

  7. Python入门篇-装饰器

    Python入门篇-装饰器 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.装饰器概述 装饰器(无参) 它是一个函数 函数作为它的形参 返回值也是一个函数 可以使用@functi ...

  8. Sonar中的坏习惯详解

    22种代码的坏味道,一句话概括: 如果一段代码是不稳定或者有一些潜在问题的,那么代码往往会包含一些明显的痕迹. 正如食物要腐坏之前,经常会发出一些异味一样. 我们管这些痕迹叫做“代码异味”. 参考资料 ...

  9. storm整合kafka storm-kafka-client

    pom.xml-注意jar-log4j---------------------<dependencies> <dependency> <groupId>org.a ...

  10. 我感觉这个书上的微信小程序登陆写得不好

    基本功能是OK,但是感觉传的数据太多,不安全,需要改写. App({ d: { hostUrl: 'http://www.test.com/index.php', //请填写您自己的小程序主机URL ...