java多线程03-----------------volatile内存语义
java多线程02-----------------volatile内存语义
volatile关键字是java虚拟机提供的最轻量级额的同步机制。由于volatile关键字与java内存模型相关,因此,我们在介绍volatile关键字之前,对java内存模型进行更多的补充(之前的博文也曾介绍过)。
1. java内存模型(JMM)
JMM是一种规范,主要用于定义共享变量的访问规则,目的是解决多个线程本地内存与共享内存的数据不一致、编译器处理器的指令重排序造成的各种线程安全问题,以保障多线程编程的原子性、可见性和有序性。
JMM规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程中的工作内存中存储了该线程用到的变量的主内存的拷贝,各线程对变量的所有操作都必须在工作内存中进行,
线程之间的变量值的传递都必须通过主内存来进行。
JMM定义了8中操作实现主内存与工作内存的交互协议:
1)lock:作用于主内存,它把一个变量标识为一条线程的独占状态。
2)unlock:作用于主内存,它把一个处于锁定状态的变量的释放出来。
3)read:作用于主内存,它把一个变量的值从主内存传输到线程的工作内存中。
4)load:作用于工作内存,它把从主内存中read到的值放入工作内存的变量副本中。
5)use:作用于工作内存,它把一个变量的值从主内存传递给执行引擎
6)assign:作用与工作内存,它把一个从执行引擎接收到的值赋值给工作内存的变量。
7)store:作用于工作内存,把工作内存中一个变量的值传送到主内存。
8)write:作用于主内存,它把store操作从工作内存中得到的值放入主内存中的变量中。
这8中操作以及对着8中操作的规则的限制就能确定哪些内存访问在并发条件下是线程安全的,这种方式比较繁琐,jdk1.5之后提出了提出了happens-before规则来判断线程是否安全。
可以这么理解,happens-before规则是JMM的核心.Happens-before就是用来确定两个操作的执行顺序。这两个操作可在同一线程中,也可以在两个线程中。
happens-before规定:如果一个操作happens-before另个一操作,那么第一个操作的结果对第二个操作可见(但这并不意味着处理器必须按照happens-before顺序执行,只要不改变执行结果,可任意优化)。happens-before规则已在前边博文中介绍,这里不再重复(http://www.cnblogs.com/gdy1993/p/9117331.html)
JMM内存规则仅仅是一种规则,规则的最终落实是通过java虚拟机、编译器以及处理器一同协作来落实的,而内存屏障是java虚拟机、编译器、处理器之间沟通的纽带。
而java原因封装了这些底层的具体实现与控制,提供了synchronized、lock和volatile等关键字的来保障多线程安全问题。
2. volatile关键字
(1)volatile对可见性的保证
在介绍volatile关键字之前,先来看这样一段代码:
//线程1
boolean stop = false;
while(!stop) {
doSomething();
} //线程2
stop = true;
有两个线程:线程1和线程2,线程1在stop==false时,不停的执行doSomething()方法;线程2在执行到一定情况时,将stop设置为true,将线程1中断,很多人采用这种方式中断线程,但这并不是安全的。因为stop作为一个普通变量,线程2对其的修改,并不能立刻被线程1所感知,即线程1对stop的修改仅仅在自己的工作内存中,还没来的急写入主内存,线程2工作内存中的stop并未修改,可能导致线程无法中断,虽然这种可能性很小,但一旦发生,后果严重。
而使用volatile变量修饰就能避免这个问题,这也是volatile第一个重要含义:
volatile修饰的变量,能够保证不同线程对这个变量操作的可见性,即一个线程修改了这个变量的值,这个新值对于其他线程是立即可见的。
volatile的对可见性保证的原理:
对于volatile修饰的变量,当某个线程对其进行修改时,会强制将该值刷新到主内存,这就使得其他线程对该变量在各自工作内存中的缓存无效,因而在其他线程对该变量进行操作时,必须从主内存中重新加载
(2)volatile对原子性的保障?
首先来看这样一段代码(深入理解java虚拟机):
public class VolatileTest {
public static volatile int race = 0; public static void increase() {
race++;
} public static final int THREAD_COUNT = 20; public static void main(String[] args) {
Thread[] threads = new Thread[THREAD_COUNT];
for (Thread t : threads) {
t = new Thread(new Runnable() { @Override
public void run() {
for(int i = 0; i < 10000; i++) {
increase();
}
}
});
t.start();
} while(Thread.activeCount() > 1) {
Thread.yield();
} System.out.println(race);//race < 200000 }
}
race是volatile修饰的共享变量,创建20个线程对这个共享变量进行自增操作,每个线程自增的次数为10000次,如果volatile能够保证原子性的话,最终race的结果肯定是200000。但结果不然,每次程序运行race'的值总是小于200000,这也侧面证明了volatile并不能保证共享变量操作的原子性。原理如下:
线程1读取了race的值,然后cp分配的时间片结束,线程2此时读取了共享变量的值,并对race进行自增操作,并将操作后的值刷新到主内存,此时线程1已经读取了race的值,因此保留的依然是原来的值,此时这个值已是旧值,对race进行自增操作后刷新到主内存,因此主内存中的值也是旧值。这也是volatile仅仅能保障读到的是相对新值的原因。
(3)volatile对有序性的保障
首先来看这样一段代码:
//线程1
boolean initialized = false;
context = loadContext();
initialized = true; //线程2
while(!initialized) {
sleep();
} doSomething(context);
线程2在initialized变量为true时,使用context变量完成一些操作;线程1负责加载context,并在加载完成后将initialized变量设为true。但是,由于initialized只是一个普通变量,普通变量仅仅能够保证在该方法的执行过程中,所有依赖赋值结果的地方都能获得正确的值,而不能保证变量的赋值顺序与程序代码的执行顺序一致。因此就可能出现这样一种情况,当线程1将initialized变量设为true时,context依然没有加载完成,但线程2由于读到initialized为true,就可能执行了doSomething()方法,可能会产生非常奇怪的效果。
而volatile的第二个语义就是禁止重排序:
写volatile变量的操作与该操作之前的任何读写操作都不会被重排序;
读volatile变量操作与该操作之后的任何读写操作都不会重排序。
(4) volatile的底层实现原理
java语言底层是通过内存屏障来实现volatile语义的。
对于volatile变量的写操作:
①java虚拟机会在该操作之前插入一个释放屏障(loadstore+storestore),释放屏障禁止了volatile变量的写操作与该操作之前的任何读写操作的重排序。
②java虚拟机会在该操作之后插入一个存储屏障(storeload),存储屏障使得对volatile变量的写操作能够同步到主内存。
对于volatile变量的读操作:
③java虚拟机会在该操作之前插入一个loadload,使得每次对volatile变量的读取都从主内存中重新加载(刷新处理器缓存)
④java虚拟机会在该操作之后插入一个获得屏障(loadstore+loadload),使得volatile后的任何读写操作与该操作进行重排序。
①③保障可见性,②④保障有序性。
(5)volatile关键字与happens-before的关系
Happens-before规则中的volatile规则为:对于一个volatile域的写happens-before后续每一个针对该变量的读操作。
写线程执行write(),然后读线程执行read()方法,图中每个箭头都代表一个happens-before关系,黑色箭头是根据程序顺序规则,蓝色箭头根据volatile规则,红色箭头是根据传递性推出的,即操作2happens-before操作3,即对volatile共享变量的更新操作排在后续读取操作之前,对volatile变量的修改对后续volatile变量的读取可见。
java多线程03-----------------volatile内存语义的更多相关文章
- java关键字volatile内存语义详细分析
volatile变量自身具有下列特性. 1.可见性.对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写 入. · 2.原子性:对任意单个volatile变量的读/ ...
- java多线程关键字volatile的使用
java多线程关键字volatile的作用是表示多个线程对这个变量共享. 如果是只读的就可以直接用,写数据的时候要注意同步问题. 例子: package com.ming.thread.volatil ...
- Java多线程:volatile 关键字
一.内存模型的相关概念 大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存 ...
- Java并发:volatile内存可见性和指令重排
volatile两大作用 1.保证内存可见性 2.防止指令重排 此外需注意volatile并不保证操作的原子性. (一)内存可见性 1 概念 JVM内存模型:主内存和线程独立的工作内存 Java内存模 ...
- 内存屏障和volatile内存语义的实现
趁周末,把以前的书拿出来,再翻一番,顺便做个笔记: 内存屏障:用来控制和规范cpu对内存操作的顺序的cpu指令. 内存屏障列表: 1.loadload:确保“前者数据装载”先于“后者装载指令”: 2. ...
- java多线程关键字volatile、lock、synchronized
--------------------- 本文来自 旭日Follow_24 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/xuri24/article/detail ...
- volatile内存语义
全面理解Java内存模型(JMM)及volatile关键字 volatile的内存语义 Volatile读写所建立的happens-before关系Volatile读写的内存语义 锁: 获取和释放Vo ...
- Java多线程中的内存模型
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6536131.html 一:现代计算机的高速缓存 在计算机组成原理中讲到,现代计算机为了匹配 计算机存储设备的 ...
- [心得笔记]Java多线程中的内存模型
一:现代计算机的高速缓存 在计算机组成原理中讲到,现代计算机为了匹配 计算机存储设备的读写速度 与 处理器运算速度,在CPU和内存设备之间加入了一个名为Cache的高速缓存设备来作为缓冲:将运算需要 ...
- Java多线程编程——volatile关键字
(本篇主要内容摘自<Java多线程编程核心技术>) volatile关键字的主要作用是保证线程之间变量的可见性. package com.func; public class RunThr ...
随机推荐
- Linux学习-备份要点
备份资料的考虑 老实说,备份是系统损毁时等待救援的救星!因为你需要重新安装系统时, 备份的好坏会影响到你 系统复原的进度!事实上,系统有可能由于不预期的伤害而导致系统发生错误! 什么是不预期的伤害呢? ...
- 实现hadoop自动安装包
最近研究hadoop,需要安装多个dadanode,想从重复劳动解脱出来,只能自己实现自动安装包,开始考虑使用shell.python等实现,感觉比较费时间,用installshield又有点牛刀小试 ...
- [转] 重定向 CORS 跨域请求
非简单请求不可重定向,包括第一个preflight请求和第二个真正的请求都不行. 简单请求可以重定向任意多次,但如需兼容多数浏览器,只可进行一次重定向. 中间服务器应当同样配置相关 CORS 响应头. ...
- Selenium WebDriver- 使用显示等待,判断搜狗的输入框是否显示,按钮是否可点击,然后在输入光荣之路搜索词,然后在点击搜索。
#encoding=utf-8 from selenium import webdriver import time from selenium.webdriver.common.by import ...
- day03_05 Python程序文件执行和与其他编程语言对比
python在windows操作系统上是没有的,但是在linux上默认就有python 执行python程序的方式有两种: 1.交互器,缺点 程序不能永久保存,主要用于简单的语法测试 2.文件执行 对 ...
- 国际化多语言(本地化)缩写 NLS API
NLS Information for Windows 7 LCID Culture Identifier Culture Name Locale Language Country/Region La ...
- EF的三种模式
1.DateBase First(数据库优先) 2.Model First(模型优先) 3.Code First(代码优先) 当然,如果把Code First模式的两种具体方式独立出来,那就是四种了. ...
- Linux Shell系列教程之(三)Shell变量
本文是Linux Shell系列教程的第(三)篇,更多shell教程请看:Linux Shell系列教程 Shell作为一种高级的脚本类语言,也是支持自定义变量的.今天就为大家介绍下Shell中的变量 ...
- C++中使用Curl和JsonCpp调用有道翻译API实现在线翻译
使用C++开发一个在线翻译工具,这个想法在我大脑中过了好几遍了,所以就搜了下资料,得知网络上有很多翻译API,这里我选择我平时使用较多的有道翻译API进行在线翻译工具开发的练习.翻译API返回的结果常 ...
- 【bzoj1043】[HAOI2008]下落的圆盘 计算几何
题目描述 有n个圆盘从天而降,后面落下的可以盖住前面的.求最后形成的封闭区域的周长.看下面这副图, 所有的红色线条的总长度即为所求. 输入 第一行为1个整数n,N<=1000接下来n行每行3个实 ...