JVM并发机制的探讨——内存模型、内存可见性和指令重排序
并发本来就是个有意思的问题,尤其是现在又流行这么一句话:“高帅富加机器,穷矮搓搞优化”。 从这句话可以看到,无论是高帅富还是穷矮搓都需要深入理解并发编程,高帅富加多了机器,需要协调多台机器或者多个CPU对共享资源的访问,因此需要了解并 发,穷矮搓搞优化需要编写各种多线程的代码来压榨CPU的计算资源,让它在同一时刻做更多的事情,这个更需要了解并发。
在我前一篇关于并发的文章http://my.oschina.net/chihz/blog/54731中 提到过管程,管程的特色是在编程语言中对并发的细节进行封装,使程序员可以直接在语言中就得到并发的支持,而不必自己去处理一些像是控制信号量之类容易出 错且繁琐的细节问题。一些语言是通过在编译时解开语法糖的方式去实现管程,但Java在编译后生成的字节码层面上对并发仍然是一层封装,比如 syncrhonized块在编译之后只是对应了两条指令:monitorenter和monitorexit。更多的并发细节是在JVM运行时去处理 的,而不是编译。这篇文章主要是针对JVM处理并发的一些细节的探讨。
JAVA内存模型
JVM需要实现跨平台的支持,它需要有一套自己的同步协议来屏蔽掉各种底层硬件和操作系统的不同,因此就引入了Java内存模型。对于Java来说开发者
并不需要关心任何硬件细节,因此没有多核CPU和高速缓存的概念,多核CPU和高速缓存在JVM中对应的是Java语言内置的线程和每个线程所拥有的独立
内存空间,Java内存模型所规范的也就是数据在线程自己的独立内存空间和JVM共享内存之间同步的问题。下面这两张图说明了硬件平台和JVM内存模型的
相似和差异之处。

Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,线程只能访问自己的工作内存,不可以访问其它线程的
工作内存。工作内存中保存了主内存共享变量的副本,线程要操作这些共享变量,只能通过操作工作内存中的副本来实现,操作完毕之后再同步回到主内存当中。如
何保证多个线程操作主内存的数据完整性是一个难题,Java内存模型也规定了工作内存与主内存之间交互的协议,首先是定义了8种原子操作:
我们可以看到,要保证数据的同步,lock和unlock定义了一个线程访问一次共享内存的界限,有lock操作也必须有unlock操作,另外一些操作
也必须要成对出现才可以,像是read和load、store和write需要成对出现,如果单一指令出现,那么就会造成数据不一致的问题。Java内存
模型也针对这些操作指定了必须满足的规则:
变量在同一时刻只允许一个线程对其进行lock,有多少次lock操作,就必须有多少次unlock操作。在lock操作之后会清空此变量在工作内存中原
先的副本,需要再次从主内存read-load新的值。在执行unlock操作前,需要把改变的副本同步回主存。
内存可见性
通过上面Java内存模型的概述,我们会注意到这么一个问题,每个线程在获取锁之后会在自己的工作内存来操作共享变量,在工作内存中的副本回写到主内存,
并且其它线程从主内存将变量同步回自己的工作内存之前,共享变量的改变对其它线程是不可见的。那么很多时候我们需要一个线程对共享变量的改动,其它线程也
需要立即得知这个改动该怎么办呢?比如以下的情景,有一个全局的状态变量open:
所以对于上面的情景,要求一个线程对open的改变,其他的线程能够立即可见,Java为此提供了volatile关键字,在声明open变量的时候加入
volatile关键字就可以保证open的内存可见性,即open的改变对所有的线程都是立即可见的。volatile保证可见性的原理是在每次访问变
量时都会进行一次刷新,因此每次访问都是主内存中最新的版本。
指令重排序
想到有一条古老的原则很适合用在这个地方,那就是先要保证程序的正确然后再去优化性能。此处由于重排序产生的错误显然要比重排序带来的性能优化要重要的
多。要解决重排序问题还是通过volatile关键字,volatile关键字能确保变量在线程中的操作不会被重排序而是按照代码中规定的顺序进行访问。
最后的总结
这篇文章简单的介绍了Java内存模型、内存可见性和指令重排序。不过最后看来其实主要是在解释volatile这个关键字,个人感觉volatile关
键字是Java当中最令人困惑和最难理解的关键字。相对于synchronized块的代码锁,volatile应该是提供了一个轻量级的针对共享变量的
锁,当我们在多个线程间使用共享变量进行通信的时候需要考虑将共享变量用volatile来修饰,对于需要使用volatile的各种情景,看到IBM
Developer Works上有一篇文章总结的很不错,推荐一下: http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
补充说明:64位long和double
在JVM规范中Java内存模型要求lock、unlock、read、load、assign、use、store、write这8个操作必须是原子
的,但是对于64位的long和double来说,如果没有被volatile修饰符修饰,那么可以不是原子的,注意是可以,即虚拟机在实现的时候可以选
择是否是原子操作。目前几乎所有的商用虚拟机都将此实现为原子操作,因此不必每次用到它们都去加volatile修饰。
JVM并发机制的探讨——内存模型、内存可见性和指令重排序的更多相关文章
- 轻松学JVM(二)——内存模型、可见性、指令重排序
上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存 ...
- JVM学习--(二)内存模型、可见性、指令重排序
我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存模型 首先我们思考一下一个java线程要向另外一个线程进行通信,应该怎么做,我们再 ...
- 深入理解JVM(二)——内存模型、可见性、指令重排序
上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存 ...
- 深入理解JVM一内存模型、可见性、指令重排序
一.内存模型 首先我们思考一下一个java线程要向另外一个线程进行通信,应该怎么做,我们再把需求明确一点,一个java线程对一个变量的更新怎么通知到另外一个线程呢?我们知道java当中的实例对象.数组 ...
- 内存可见性,指令重排序,JIT。。。。。。从一个知乎问题谈起
在知乎上看到一个问题<java中volatile关键字的疑惑?>,引起了我的兴趣 问题是这样的: package com.cc.test.volatileTest; public clas ...
- Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
- 深入理解JMM(Java内存模型) --(二)重排序
[转载自并发编程网 – ifeve.com 原文链接:http://ifeve.com/tag/jmm/] 数据依赖性 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存 ...
- JVM知识(三):内存模型和可见性
这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况.来聊聊java线程对一个变量的更新怎么通知另一个线程,及volatile的作用和 ...
- 转: 【Java并发编程】之十七:深入Java内存模型—内存操作规则总结
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17377197 主内存与工作内存 Java内存模型的主要目标是定义程序中各个变量的访问规则, ...
随机推荐
- Visual Studio 2012下Box2D开发环境设置
Cocos2d-x 3.x默认情况下采用的物理引擎是Chipmunk,如果我们要使用Box2D引擎,需要进行一些设置和调整,而且不同的开发平台下这些设置也有所不同.由于本书在此之前介绍的都是基于微软的 ...
- (转)卸载和安装LINUX上的JDK
卸载默认的: 用root用户登陆到系统,打开一个终端输入 # rpm -qa|grep gcj 显示内容其中包含下面两行信息 # java-1.4.2-gcj-compat-1.4.2.0-27jpp ...
- jqGrid API 相关
取消所有选中的行: $("jqgridtableid").trigger("reloadGrid"): 设定选中行,可设定多行选中: $("jqgri ...
- javascript笔记—— call 简单理解
call 方法 请参阅 应用于:Function 对象 要求 版本 5.5 调用一个对象的一个方法,以另一个对象替换当前对象. call([thisObj[,arg1[, arg2[, [,.argN ...
- mac安装memcache
1.wget http://blog.s135.com/soft/linux/nginx_php/memcache/memcache-2.2.5.tgz 2.tar zxvf memcache-2.2 ...
- ZENCART 打开/关闭日志文件
优秀的php开源程序很多都只带生成日志文件的功能,这类功能的开发可以帮助到站长在调试网站的时候及时的改正网站存在的错误,但是这类错误日志由来并非网站出现什么严重不可挽救的错误,大部分是一些未定义变量这 ...
- jQuery 图片轮播的代码分离
以前遇到过jQuery实现列表自动滚动,这次的图片轮播在原理上与之相同,只有一些细微的差别,就是需要在图片的右下角显示当前图片的序号. html代码,以及对应的css代码: <div id=&q ...
- 医学影像工作站程序ProDicom的说明
转载 http://blog.csdn.net/prodicom/article/details/4015064 注意:以下内容为转载,但保留了第一人称,请注意,以免造成不必要的麻烦. 医网联影像工作 ...
- 【转】O'Reilly Java系列书籍建议阅读顺序(转自蔡学庸)
Learning Java the O'Reilly's Way (Part I) Java 技术可以说是越来越重要了,不但可以用在计算机上,甚至连电视等家电用品,行动电话.个人数字助理(PDA)等电 ...
- SQL*Loader使用详解(一)
1.SQL*Loader介绍 1)SQL*Loader是一个从外部文件指加载数据到Oracle数据库的工具.语法类似于DB2的Load语法,但SQL*Loader支持各种load格式.选择性load和 ...