Java内存模型JMM与可见性
Java内存模型JMM与可见性
标签(空格分隔): java
1 何为JMM
JMM:通俗地讲,就是描述Java中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。
结合上图,先介绍几个概念:
主内存:保存了所有的变量。
共享变量:如果一个变量被多个线程使用,那么这个变量会在每个线程的工作内存中保有一个副本,这种变量就是共享变量。
工作内存:每个线程都有自己的工作内存,线程独享,保存了线程用到了变量的副本(主内存共享变量的一份拷贝)。工作内存负责与线程交互,也负责与主内存交互。
JMM对共享内存的操作做出了如下两条规定:
- 线程对共享内存的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写;
- 不同线程无法直接访问其他线程工作内存中的变量,因此共享变量的值传递需要通过主内存完成。
2 共享变量在线程间的可见性
由此可见不同线程都是直接操作自身工作内存中的副本,因此可能导致共享变量的修改在线程间不可见,所谓不可见,是指一个线程对共享变量的修改不能及时地被其他线程看到。导致共享变量在进程间不可见的原因有以下几个:
- 指令重排序 & 线程交叉执行
- 共享变量更新后的值没有在工作内存和主内存间及时更新
线程交叉执行:主要指线程调度。
指令重排序:为了发挥CPU性能,指令执行顺序可能与书写的不同,分为编译器优化的重排序(编译器优化),指令集并行重排序(处理器优化),内存系统的重排序(处理器优化)。
共享变量更新:如果想让线程A对共享变量的修改被线程B看到,需要以下步骤:把线程A的工作内存中更新过的变量刷新到主内存中,再将主内存中最新的共享变量的值刷新到线程B的工作内存中。如果更新不及时,则会导致共享变量的不可见,数据不准确,线程不安全。
说到重排序,就不得不说一下as-if-serial语义:无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致。(编译器,运行时和处理器都会保证Java在单线程下遵循as-if-serial语义)
下面看一段代码:
public class PossibleReordering {
static int x = 0, y = 0;
static int a = 0, b = 0;
public PossibleReordering() {}
public static void main(String[] args) throws InterruptedException {
int result[] = new int[4];
for(int i = 0; i < 1000000; i++){
Thread one = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
x=y=a=b=0;
one.start();
two.start();
one.join();
two.join();
//注意,此时线程one和two均以结束,其对x,y的修改已经写回到主内存
int r = (x << 1) | (y);
result[r]++;
}
System.out.println(Arrays.toString(result));
}
}
该段代码可能输出的x和y有4种组合,分别为(0,0) (0,1) (1,0) (1,1)。一次典型的运行输出如下:[4, 941466, 58524, 6]。该输出代表(0,0)组合产生了4次,(0,1)组合产生了941466次,(1,0)组合产生了58524次,(1,1)组合产生了6次。
各种组合及其可能的原因如下表:
组合 | 可能的产生原因 |
---|---|
0 1 | 线程one在two开始之前就完成 |
1 0 | 线程two在one开始前就完成 |
1 1 | 线程one和two交叉执行的结果 |
0 0 | 乱序执行或共享变量更新到主内存不及时 |
由此可见,因此在没有正确同步的情况下,即是要推断最简单的并发程序的行为也很困难。
可能的一种乱序执行情况如下图所示:
重排序不会导致单线程的内存可见性问题,但多线程交错执行时,可能导致可见性问题,那么如何解决线程间对共享变量修改的可见性问题呢?
3 Java在语言层面实现可见性的两种方式
使用synchronized实现可见性:
Java中synchronized关键字有两重含义,一是大家所熟知的实现原子性,二就是实现内存可见性。
synchronized可见性规范:
- 线程解锁前必须把共享变量的最新值刷新到主内存中
- 线程加锁时,将清空工作内存中共享变量的值,从而需要从主内存中重新读取最新值。
因此,线程解锁前对共享变量的修改在下次加锁时对其他线程可见。整个过程如下:获得互斥锁-》清空工作内存-》从主内存拷贝-》执行代码-》写回主内存-》释放互斥锁。
与此同时,synchronized还会限制编译器、运行时和硬件对内存操作重排序的方式,从而在实施重排序时不会破坏JMM提供的可见性保证。
共享变量在线程间不可见的原因 | synchronized解决方案 |
---|---|
重排序 & 线程交叉执行 | 原子性(结合as-if-serial语义) |
共享变量未及时更新 | 通过synchronized可见性规范 |
使用volatile实现可见性:
Java中的volatile可以保证volatile变量的可见性,但不保证复合操作的原子性(如++)
volatile可见性规范:
- 对volatile变量执行写操作时,会在写操作后加入一条store写屏障指令,强制将缓存刷新到主内存中
- 对volatile变量执行读操作时,会在读操作前加入一条load读屏障指令,强制使缓冲区缓存失效,所以会从主内存读取最新值。
- 防止指令重排序。
通俗来讲:volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存。这样在任何时刻,不同的线程总能看到该变量的最新值。
共享变量在线程间不可见的原因 | volatile解决方案 |
---|---|
重排序 & 线程交叉执行 | 防止指令重排序 |
共享变量未及时更新 | 通过volatile可见性规范 |
synchronized与volatile对比:
- volatile不需要加锁,比synchronized轻量,不会阻塞线程。
- 从内存可见性角度来看,volatile读相当于加锁,volatile写相当于解锁。
- synchronized可以保证可见性+原子性。volatile只能保证可见性,不能保证原子性。
参考资料
慕课网:细说Java多线程之内存可见性
Java并发编程实战
程晓明:深入理解Java内存模型(一)——基础
Java内存模型JMM与可见性的更多相关文章
- Java内存模型JMM 高并发原子性可见性有序性简介 多线程中篇(十)
JVM运行时内存结构回顾 在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下 整体结构如下图所示,大致分为五大块 而对于方法区中的数据,是属于所有线程共享的数据结构 而对于虚拟机栈中数据 ...
- 多线程并发之java内存模型JMM
多线程概念的引入是人类又一次有效压寨计算机的体现,而且这也是非常有必要的,因为一般运算过程中涉及到数据的读取,例如从磁盘.其他系统.数据库等,CPU的运算速度与数据读取速度有一个严重的不平衡,期间如果 ...
- 全面理解Java内存模型(JMM)及volatile关键字(转载)
关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...
- 全面理解Java内存模型(JMM)及volatile关键字(转)
原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...
- 对多线程java内存模型JMM
多线程概念的引入体现了人类重新有效压力寨计算机.这是非常有必要的,由于所涉及的读数据的过程中的一般操作,如从磁盘.其他系统.数据库等,CPU计算速度和数据读取速度已经严重失衡.假设印刷过程中一个线程将 ...
- 深入理解Java内存模型JMM与volatile关键字
深入理解Java内存模型JMM与volatile关键字 多核并发缓存架构 Java内存模型 Java线程内存模型跟CPU缓存模型类似,是基于CPU缓存模型来建立的,Java线程内存模型是标准化的,屏蔽 ...
- Java并发编程:Java内存模型JMM
简介 Java内存模型英文叫做(Java Memory Model),简称为JMM.Java虚拟机规范试图定义一种Java内存模型来屏蔽掉各种硬件和系统的内存访问差异,实现平台无关性. CPU和缓存一 ...
- 深入理解Java内存模型JMM
本文转载自深入理解Java内存模型JMM JMM基础与happens-before 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执 ...
- 什么是Java内存模型(JMM)
什么是java内存模型 缓存一致性问题 在现代计算机中,因为CPU的运算速度远大于内存的读写速度,因此为了不让CPU在计算的时候因为实时读取内存数据而影响运算速度,CPU会加入一层缓存,在运算之前缓存 ...
随机推荐
- 在Windows Server 2012服务器上安装可靠多播协议
为什么要安装可靠多播协议? 答:随着因特网的发展,出现了视频点播.电视会议.远程学习.计算机协同工作等新业务.传统的点到点通信方式,不仅浪费大量的网络带宽,而且效率很低.一种有效利用现有带宽的技术 ...
- 问题-[Delphi]提示Can't load package:dclite70.bpl解决方法
问题现象:提示Can't load package:dclite70.bpl 问题原因:全是Window2003的Data Execution Prevention(DEF数据执行保护)造成的. 解决 ...
- 引入less报错解决方法以及浏览器设计不同的地方
XMLHttpRequest cannot load file:///C:/Users/PAXST/Desktop/805/first.less. Cross origin requests are ...
- python selenium启动浏览器打开百度搜索
python selenium打开百度搜索 #!usr/bin/python from selenium import webdriver import time browser = webdrive ...
- lettCode-Array
1 Remove Element lintcode-172 描述: 删相同元素,反现有长度 记忆:标不同元素,反标记值 public int removeElement(int[] a, i ...
- Hyper-V介绍
Hyer-v主机是高端虚拟主机用户的最佳选择.您不再受其他用户程序对您造成的影响,您将得到的是更加公平的资源分配,远远低于虚拟主机的故障率.Hyper-V的分区包含两种:父分区和客户分区.Hyper- ...
- 分布式助手Zookeeper(四)
分布式助手Zookeeper(四)博客分类: Zookeeper zookeeper配置同步zookeeper编程 Zookeeper是分布式环境下一个重要的组件,因为它能在分布式环境下,给我带来很多 ...
- 在不同平台上CocosDenshion所支持的音频格式
在大多数平台上,cocos2d-x调用不同的SDK API来播放背景音乐和音效.CocosDenshion在同一时间只能播放一首背景音乐,但是能同时播放多个音效. 背景音乐 Platform supp ...
- hdu1016JAVA
import java.util.Arrays;import java.util.Scanner;public class Main { public static int kase=0,n; pub ...
- fuck'em
不要说GUNGHO的游戏,连逆转三国这种都没玩过,还是做手游的,表现的那么冠冕堂皇,还不只是个常规的做软件的而已.只是以做软件的程度来做游戏,能做出的是个JB.