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 ...
随机推荐
- Hadoop4.2HDFS测试报告之一
1.1 测试方案 1.1.1 测试目标 为了验证本地文件系统和HDFS存储能力对比,将1G文件组存储进各个文件系统,记录存储任务消耗的时间. l 测试HDFS的高可用性和高稳定性 l 测试 ...
- Python中的魔法函数__repr__和__str__的实质性区别
str 和 repr 方法:是自定义类的字符串描述,这两种都是比较 Pythonic 的方式去控制对象转化为字符串的方式. 调用这两个方法,返回的都是字符串.但是这两个方法又有一些区别 ** 1 两种 ...
- mac常用软件,自用找了很久的分享一下相信很多人需要
CleanMyMac 3.1.1.dmg比较好用的清理软件.破解版!http://pan.baidu.com/s/1i4mo7jvNTFS读写 Tuxera NTFS for Mac.rar也是破解的 ...
- wamp搭建的服务器远程无法访问的问题
最近在一台win2003的服务器上安装配置好了wamp,服务启动正常,服务器本机访问localhost正常,但是我自己的电脑(相对于服务器来说是远程机器)访问时,提示显示You don't have ...
- webdriver高级应用- 精确比较页面截图图片
判断两张图是否完全一致,如果存在任何不一致,会认为图片不匹配,代码如下: #encoding=utf-8 from selenium import webdriver import unittest, ...
- 是男人就过 8 题--Pony.AI A AStringGame
链接:https://www.nowcoder.com/acm/contest/92/A来源:牛客网 AStringGame 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 26214 ...
- [错误处理]: How to deal with chrome failing to launch GPU process
https://github.com/jupyter/notebook/issues/2836 "export BROWSER=google-chrome" command wor ...
- JAVA调用oracle存储过程实例
1.创建添加存储过程 CREATEORREPLACEPROCEDURE stu_proc(v_id INNUMBER, v_name INVARCHAR2, v_age INNUMBER) AS BE ...
- Bootstrap-table custome-ajax用法
<div id="toolbar"> <div class="form-inline" role="form"> & ...
- 刷题总结——大工程(bzoj3611)
题目: Description 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道. 我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上. 在 2 个国家 a,b 之间建 ...