Java内存模型以及线程安全的可见性问题
Java内存模型 VS JVM运行时数据区
首先Java内存模型(JMM)和JVM运行时数据区并不是一个东西,许多介绍Java内存模型的文章描述的堆,方法区,Java虚拟机栈,本地方法栈,程序计数器这东西并不是Java内存模型的内容而是JVM运行时数据区的内容。
要理解二者的区别就要了解《Java虚拟机规范》和《Java语言规范》。我们知道Java虚拟机上并不知只有Java语言,像JRuby, ,Scala,Kotlin,Groovy等也都运行在Java虚拟机上,而这些语言想要在Java虚拟机上运行就要遵守《Java虚拟机规范》,而JVM运行时数据区就是《Java虚拟机规范》的内容。而《Java语言规范》就只是针对Java语言的规范,它对Java内存模型做了详细的描述。
什么是Java内存模型(JMM)?
要了解Java内存模型,首先要了解什么是内存模型,之间在CPU缓存和内存屏障 中我们了解到缓存一致性问题以及处理器优化的指令重排序问题。为了保证并发编程中可以满足原子性、可见性及有序性。有一个重要的概念,那就是——内存模型。它解决了 CPU 多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。而Java内存模型就是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题的一种规范。目的是保证并发编程场景中的原子性、可见性和有序性。
Java内存模型可以分为线程栈(或者叫工作内存,它是每个线程所独有的)和堆(或者叫主内存,与JVM运行时数据区的堆并不是一个概念,它是所线程共享的),其大致逻辑图如下:
JMM中的具体内容
Shared Variables定义
可以在线程之间共享的内存称为共享内存或堆内存
所有实例字段,静态字段和数组元素都存储在共享内存,这些字段和数组就是共享变量
冲突:如果至少有一个访问是写操作,那么对同一个变量的两次访问是冲突的
这些能被多个线程访问的共享变量是内存模型规范的对象
线程间操作
线程间操作指一个线程执行的操作可被其他线程感知或被其他线程直接影响
Java内存模型只描述线程间操作,不描述线程内操作,线程内操作按照线程内语义执行
线程间操作有:
- read操作(一般读,即非volatile读)
- write操作(一般写,即非volatile写)
- volatile read
- volatile write
- Lock,Unlock
- 线程的第一个和最后一个操作
- 外部操作
对同步规则的定义
- 对volatile变量V的写入,与所有其它线程后续对V的读同步
- 对于监视器m的解锁与所有后续操作对于m的加锁同步
- 对于每个属性写入默认值(0,false, null)与每个线程对其进行的操作同步
- 启动线程的操作与线程中的第一个操作同步
- 线程T2的最后操作与线程T1发现T2已经结束同步
- 如果线程T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步
happens-before先行发生原则
happens-before关系用于描述两个有冲突的动作之间的顺序,如果一个action happens before 另一个action,则第一个操作对第二个操作可见,JVM需要实现如下happens-before规则:
- 某个线程中的每个动作都happens-before该线程中该动作后面的操作
- 某个管程中的unlock动作happens-before同一个管程上后续的lock操作
- 对某个volatile字段的写操作happens-before每个后续对该volatile字段的读操作
- 在某个对象上调用start()方法happens-before被启动线程的任意动作
- 如果在线程t1中成功执行了t2.join(),则t2中的所有操作对t1可见
- 如果某个动作a happens-before动作b,且b happens-before动作c,则a happens-before c
final在JMM中的处理
final在该对象的构造函数中设置对象的字段,当线程看到该对象时,将始终看到该对象的final字段的正确构造版本。如果在构造函数中设置字段后发生读取,则会看到该final字段分配的值,否则它将看到默认值。读取该对象的final成员变量之前,先要读取共享对象。
通常被 static final修饰的字段, 不能被修改。然而System.in, System.out, System.err被static final修饰却可以修改,遗留问题,必须通过set方法改变,我们将这些字段称为写保护,以区别于普通final字段。
Word Tearing字节处理
有些处理器(尤其是早期的Alphas处理器)没有提供写单个字节的功能。在这样的处理器上更新byte数组,若只是简单的读取整个内容,更新对应的字节,然后将整个内容再写回内存,将是不合法的。这个问题有时候被称为“字分裂(word tearing)”,更新字节有难度的处理器,就需要寻求其他方式来解决。因此,编程人员需要注意,尽量不要对byte[]中的元素进行重新赋值,更不要在多线程中这样做。
可见性问题
可见性:主要是指一个线程对共享变量的写入可以被后续另一个线程读取到,也就说一个线程对共享变量的操作对另一个线程是可见的。
而可见性问题就是指一个线程对共享变量进行了写入而其他的线程却无法读取到该线程写入的结果,根据以下工作内存的缓存的模型我们可以知道,造成可见性的问题主要有两方面,一个是数据在写入的时候只是写入了缓存而没有写入主内存,一个是数据在读取的时候只是从缓存中读取到了数据而没有从主内存读取数据。
可见性问题的解决方法 — volatile关键字
volatile关键字可以保证一个线程对共享变量的修改,能够及时的被其他线程看到。
根据JMM中的happen before 和同步原则:
- 对某个volatile字段的写操作happens-before每个后续对该volatile字段的读操作
- 对volatile变量V的写入,与所有其它线程后续对V的读同步
而要满足这些条件volatile关键字就具有以下功能:
- 禁止缓存,volatile变量的访问控制符会加个ACC_VOLATILE,《Java虚拟机规范》 中的对它的描述就是“cannot be cached”
- 对volatile变量相关的指令不做重排序
Java内存模型以及线程安全的可见性问题的更多相关文章
- java内存模型与线程(转) good
java内存模型与线程 参考 http://baike.baidu.com/view/8657411.htm http://developer.51cto.com/art/201309/410971_ ...
- Java内存模型JMM 高并发原子性可见性有序性简介 多线程中篇(十)
JVM运行时内存结构回顾 在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下 整体结构如下图所示,大致分为五大块 而对于方法区中的数据,是属于所有线程共享的数据结构 而对于虚拟机栈中数据 ...
- Java并发程序设计(三) Java内存模型和线程安全
Java内存模型和线程安全 一 .原子性 原子性是指一个操作是不可中断的.即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰. 思考:i++是原子操作吗? 二.有序性 Java代 ...
- 深入理解java虚拟机-第12章Java内存模型与线程
第12章 Java内存模型与线程 Java内存模型 主内存与工作内存: java内存模型规定了所有的变量都在主内存中,每条线程还有自己的工作内存. 工作内存中保存了该线程使用的主内存副本拷贝,线程对 ...
- jvm(12)-java内存模型与线程
[0]README 0.1)本文部分文字描述转自“深入理解jvm”,旨在学习“java内存模型与线程” 的基础知识: [1]概述 1)并发处理的广泛应用是使得 Amdahl 定律代替摩尔定律称为计 ...
- (Java多线程系列七)Java内存模型和线程的三大特性
Java内存模型和线程的三大特性 多线程有三大特性:原子性.可见性.有序性 1.Java内存模型 Java内存模型(Java Memory Model ,JMM),决定一个线程对共享变量的写入时,能对 ...
- 深入理解Java虚拟机(第三版)-13.Java内存模型与线程
13.Java内存模型与线程 1.Java内存模型 Java 内存模型的主要目的是定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储到主内存和从内存中取出变量值的底层细节 该变量指的是 实例字 ...
- 一夜搞懂 | Java 内存模型与线程
前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 一.为什么要学习内存模型与线程? 并发处理的广泛应用是 Amdah1 定律代替摩尔定律成为计 ...
- Java内存模型与线程(一)
Java内存模型与线程 TPS:衡量一个服务性能的标准,每秒事务处理的总数,表示一秒内服务端平均能够响应的总数,TPS又和并发能力密切相关. 在聊JMM(Java内存模型)之前,先说一下Java为什么 ...
随机推荐
- WPF,通过修改dataGrid的cell的style,改变选中行失去焦点时的颜色 4.0可用
<Style TargetType="{x:Type DataGridCell}"> <Style.Triggers> <Trigger Proper ...
- 【Git】生成Patch和使用Patch
1.生成Patch(俗称快照) 先来看看repo manifest 的用法 <1>cd /工作目录/项目目录/.repo/manifests <2>repo manifest ...
- 【码云周刊第 32 期】程序员眼中的 Vue 与 Angular !
码云项目推荐 基于 Vue 的项目: 1.项目名称:基于 Vue.js 的 UI 组件库 项目简介:iView 是一套基于 Vue.js 的 UI 组件库,主要服务于 PC 界面的中后台产品. 项目地 ...
- Java底层知识学习:Bytecode and JMM
最近在跟着耗子哥的程序员练级指南学习Java底层知识,结合<深入理解Java虚拟机>这本书在看,写笔记,看资料,成长中…… 目前看完了第二章JMM和各内存区OOM的情况 一篇图文并茂介绍字 ...
- RedHat 7.3+ORACLE 12c RAC 使用udev绑定磁盘
在RedHat 7中,很多命令发生了改变,其中使用udev对磁盘绑定的命令也发生了变更,不再使用start_udev,而是改为了udevadm,下面具体介绍如何使用udev对磁盘进行绑定,这里对6和7 ...
- 一个Demo让你掌握Android所有控件
原文:一个Demo让你掌握Android所有控件 本文是转载收藏,侵删,出处:"安卓巴士" 下面给出实现各个组件的源代码: 1.下拉框实现--Spinner packag ...
- memcached的使用一
1.安装memcached 需要一个memcache.exe文件,打开cmd窗口,切换到可执行文件目录,执行memcache -的install命令. 2.连接服务 做测试可以打开电脑的telnet ...
- 制作Qt应用程序的插件(使用QtPlugin),对比DLL它是全平台通用的
在Qt下,插件有两种形式,一种是用于QtCreator下,扩展IDE功能.另一种是用于扩展开发者的应用.本文要讲的是后者. 定义一个纯虚类作为插件接口 #include <QtPlugin> ...
- 一条命令,秒秒钟完成MD5、SHA1校验,这就叫效率!
相信很多奋斗在运维战线的小伙伴们经常会遇到版本升级之类的问题.笔者之前所在的公司每次进行版本发布的时候都会附带MD5校验哈希值,每次升级之前一般都要核对MD5哈希值的,刚刚开始的时候对Linux并不是 ...
- 判断一个窗口是否被挂起(发WM_NULL消息,或者调用IsHungAppWindow API进行测试)
判断一个窗口是否被挂起了(就是没有响应了),在多窗口编程了经常会用到,在给别的窗口发消息前,为了目的窗口能确定收到消息,常常在之前先检测窗口是否被挂起了,我们以前常用的方式的是使用下面的方法: // ...