java内存模型是围绕着在并发过程中如何处理原子性,可见性跟有序性这三个问题来建立的。先看一下这三个特性:
1、原子性
由java内存模型来直接保证的原子性变量操作就是上文2中提到的8种基本操作,我们大致可以认为基本数据类型的读写是具备原子性的(long跟double不必太过在意)。如果需要一个更大范围的原子性保证,java内存模型还提供了lock跟unlock操作来满足这种需求。
原子性操作值相应的操作是单一不可分割的。例如对int变量count执行count++就不是原子操作。可以分解为3个操作:(1)读取count当前值;(2)count当前值跟1做加法运算;(3)将加完后的值赋给count变量。
多线程环境中,非原子操作可能会受其他线程干扰,比如上述例子,在执行第二个操作的时候,count的值可能已经被其它线程修改了。当然我们可以加锁或者synchronize来避免这种操作。synchronize可以实现原子性,实质是:通过该关键字所包括的临界区(Critical Section)的排他性保证在任何一个时刻只有一个线程能够执行临界区中的代码,这使得临界区中的代码代表了一个原子操作。这一点,大家基本都很清楚。但是,synchronized关键字所起到的另一个作用——保证内存的可见性(Memory Visibility),也是我们值得回顾的地方。
临界区:每个进程中访问临界资源的那段代码称为临界区(Critical Section)(临界资源是一次仅允许一个进程使用的共享资源)。每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。多个进程中涉及到同一个临界资源的临界区称为相关临界区
2、可见性
可见性指一个线程修改了共享变量的值,其它线程能够立即得知这个修改。比如volatile变量的修改,volatile关键字实现内存可见性的核心机制是:当一个线程修改了一个volatile修饰的变量的值时,该值会被写入主内存(即RAM)而不仅仅是当前线程所在的CPU的缓存区,而其他CPU的缓存区中存储的该变量的值也会因此而失效(从而得以更新为主内存中该变量的“新值”)。这就保证了其他线程访问该volatile修饰的变量时,总是可以获取到该变量的最新值。
除了volatile之外,java还有两个关键字能实现可见性:synchronized跟final。synchronize不做赘述,final的特殊规定如下:
与前面介绍的锁和volatile相比较,对final变量的读和写更像是普通的变量访问。对于final变量,编译器和处理器要遵守两个(分别对应读写)重排序规则:
1、在构造函数内对一个final变量的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
2、初次读一个包含final变量的对象的引用,与随后初次读这个final变量,这两个操作之间不能重排序。
写final变量的重排序规则
写final变量的重排序规则禁止把final变量的写重排序到构造函数之外。这个规则的实现包含下面2个方面:
1、JMM禁止编译器把final变量的写重排序到构造函数之外。
2、编译器会在final变量的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final变量的写重排序到构造函数之外 。
写final变量的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final变量已经被正确初始化过了,而普通变量不具有这个保障
对于引用类型,写final变量的重排序规则对编译器和处理器增加了如下约束:
在构造函数内对一个final引用的对象的成员变量的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
读final变量的重排序规则
在一个线程中,初次读对象引用与初次读该对象包含的final变量,JMM禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)。编译器会在读final域操作的前面插入一个LoadLoad屏障。初次读对象引用与初次读该对象包含的final变量,这两个操作之间存在间接依赖关系。由于编译器遵守间接依赖关系,因此编译器不会重排序这两个操作。
读final变量的重排序规则可以确保:在读一个对象的final变量之前,一定会先读包含这个final变量的对象的引用。在这个示例程序中,如果该引用不为null,那么引用对象的final变量一定已经被A线程初始化过了。
3、有序性
叫有序性其实有些容易误解,这个实际说的是重排序。线程内表现为串行,线程外表现为无序。就是说虽然执行过程中有重排序的优化问题,但从结果来看,单个线程执行结果并没有改变,但如果没有对公共资源加锁,多个线程并发执行的时候,会发现顺序是乱的,可能跟预期结果不同。
先行发生原则:
先行发生是指如果操作A先行发生于B,则在B操作之前,操作A产生的影响能被B观察到,影响包括修改了内存中共享变量的值,发生了消息调用了方法等。
例如:
//以下操作在线程A中执行
int i = 1;
//以下操作在线程B中执行
j = i;
//以下操作在线程C中执行
i = 2

  如果A先行发生于B,但B跟C没有先行发生关系,则B中j的值是不确定的。
  java中存在一些先行发生关系的操作,这些操作不需要同步就可以保证先行发生关系:
  1、程序次序规则。在一个线程内,书写在前面的代码先行发生于后面的。确切地说应该是,按照程序的控制流顺序,因为存在一些分支结构。
  2、Volatile变量规则。对一个volatile修饰的变量,对他的写操作先行发生于读操作。
  3、线程启动规则。Thread对象的start()方法先行发生于此线程的每一个动作。
  4、线程终止规则。线程的所有操作都先行发生于对此线程的终止检测。
  5、线程中断规则。对线程interrupt()方法的调用先行发生于被中断线程的代码所检测到的中断事件。
  6、对象终止规则。一个对象的初始化完成(构造函数之行结束)先行发生于发的finilize()方法的开始。
  7、传递性。A先行发生B,B先行发生C,那么,A先行发生C。
  8、管程锁定规则。一个unlock操作先行发生于后面对同一个锁的lock操作。

3、java内存模型特点的更多相关文章

  1. JVM学习(3)——总结Java内存模型

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 为什么学习Java的内存模式 缓存一致性问题 什么是内存模型 JMM(Java Memory Model)简 ...

  2. 浅析java内存模型--JMM(Java Memory Model)

    在并发编程中,多个线程之间采取什么机制进行通信(信息交换),什么机制进行数据的同步? 在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的. 线程之间通过共享程序公共的状态,通 ...

  3. JMM(java内存模型)

    What is a memory model, anyway? In multiprocessorsystems, processors generally have one or more laye ...

  4. 《深入理解Java内存模型》读书总结

    概要 文章是<深入理解Java内容模型>读书笔记,该书总共包括了3部分的知识. 第1部分,基本概念 包括"并发.同步.主内存.本地内存.重排序.内存屏障.happens befo ...

  5. Java内存模型深度解析:final--转

    原文地址:http://www.codeceo.com/article/java-memory-6.html 与前面介绍的锁和Volatile相比较,对final域的读和写更像是普通的变量访问.对于f ...

  6. Java内存模型深度解析:volatile--转

    原文地址:http://www.codeceo.com/article/java-memory-4.html Volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特 ...

  7. Java内存模型深度解析:顺序一致性--转

    原文地址:http://www.codeceo.com/article/java-memory-3.html 数据竞争与顺序一致性保证 当程序未正确同步时,就会存在数据竞争.java内存模型规范对数据 ...

  8. Java内存模型深度解析:基础部分--转

    原文地址:http://www.codeceo.com/article/java-memory-1.html 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何 ...

  9. 深入理解java内存模型系列文章

    转载关于java内存模型的系列文章,写的非常好. 深入理解java内存模型(一)--基础 深入理解java内存模型(二)--重排序 深入理解java内存模型(三)--顺序一致性 深入理解java内存模 ...

  10. Java内存模型深度解读

    Java内存模型规范了Java虚拟机与计算机内存是如何协同工作的.Java虚拟机是一个完整的计算机的一个模型,因此这个模型自然也包含一个内存模型——又称为Java内存模型. 如果你想设计表现良好的并发 ...

随机推荐

  1. cocos学习

    第一章 JavaScript 快速入门 1.1 变量 在 JavaScript 中,我们像这样声明一个变量: var a; 保留字 var 之后紧跟着的,就是一个变量名,接下来我们可以为变量赋值: v ...

  2. C#操作excel打印

    using System; using System.Data; using System.IO; using System.Runtime.InteropServices; using System ...

  3. 「BZOJ 1924」「SDOI 2010」所驼门王的宝藏「Tarjan」

    题意 一个\(r\times c\)的棋盘,棋盘上有\(n\)个标记点,每个点有三种类型,类型\(1\)可以传送到本行任意标记点,类型\(2\)可以传送到本列任意标记点,类型\(3\)可以传送到周围八 ...

  4. kali linux之本地提权

    已实现本地低权限帐号登录,希望获取更高权限,实现对目标进一步控制 系统帐号之间权限隔离(操作系统安全的基础,用户空间,内核空间) 系统帐号(用户帐号登录时获取权限令牌,服务帐号无需用户登录已在后台启动 ...

  5. ajax的get,post ,封装

    let ajax = new Object(); ajax.get = function(url,fn){ //创建ajax对象 let xhr = new XMLHttpRequest(); //与 ...

  6. Java面向对象之构造代码块 入门实例

    一.基础概念 1.构造代码块,给所有对象进行初始化. 2.构造函数,只给对应的对象初始化. 3.局部代码块,控制局部变量的生命周期. 二.实例代码 class Person { private int ...

  7. mysql设计-基本操作

    mysql 设计 1)服务器管理 2)数据库管理 3)表管理 4)字段管理 5)索引管理 操作 1)sql语句 2)单表操作 3)多表操作 索引 记录 字段 mysam innodb ibdata1 ...

  8. 微信小程序HTTPS - cenos apache 下安装SSL证书

    1.yum install mod_ssl 2.接下来,我们需要创建一个新目录,我们将存储服务器密钥和证书 mkdir /root/ssl 3.vi /etc/httpd/conf.d/ssl.con ...

  9. go的三个常用命令go run go build go install

    go的三个常用命令 go run go build go install 命令源码文件:含有 main函数 的文件 库源码文件:不包含 main函数 的文件, 主要用于编译成静态文件.a供其他包调用 ...

  10. UVA - 11995 模拟

    #include<iostream> #include<cstdio> #include<algorithm> #include<cstdlib> #i ...