并发一:Java内存模型和Volatile
并发一:Java内存模型和Volatile
一、Java内存模型(JMM)
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和在内存中取出变量的底层细节,是围绕着在并发过程中如何处理原子性,可见性和有序性这3个特性建立的
JMM规则
- 变量包含实例字段,静态字段,构成数组对象的元素,不包含局部变量和方法参数。
- 变量都存储在主内存上
- 每个线程都有自己的工作内存,工作内存保存了被该线程使用到的变量的主内存副本拷贝
- 线程对变量的所有操作都只能在工作内存,不能直接读写主内存的变量
- 不同线程之间无法之间访问对方工作内存中的变量

定义一个静态变量:static int a = 1;
| 线程A工作内存 | 指向 | 主内存 | 操作 |
|---|---|---|---|
| -- | -- | a = 1 | -- |
| a = 1 | <-- | a = 1 | 线程A拷贝主内存变量副本 |
| a = 3 | -- | a = 1 | 线程A修改工作内存变量值 |
| a = 3 | --> | a = 3 | 线程A工作内存变量存储到主内存变量 |
上面的一系列内存操作,在JMM中定义了8种操作来完成
JMM交互
主内存和工作内存之间的交互,JMM定义了8种操作来完成,每个操作都是原子性的
- lock(锁定):作用于主内存变量,把一个变量标识为一条内存独占的状态
- unlock(解锁):作用于主内存变量,把lock状态的变量释放出来,释放出来后才能被其他线程锁定
- read(读取):作用于主内存变量,把一个变量的值从主内存传输到工作内存中
- load(载入):作用于工作内存变量,把read操作的变量放入到工作内存副本中
- use(使用):作用于工作内存变量,把工作内存中的变量的值传递给执行引擎,每当虚拟机遇到需要这个变量的值的字节码指令时都执行这个操作
- assgin(赋值):作用于工作内存变量,把从执行引擎收到的值赋值给工作内存变量,每当虚拟机遇到需要赋值变量的值的字节码指令时都执行这个操作
- store(存储):作用于工作内存变量,把工作内存中的一个变量值,传送到主内存
- write(写入):作用于主内存变量,把store操作的从工作内存取到的变量写入主内存变量中
二、volatile
当引入线程B的时候
定义一个静态变量:static int a = 1;
| 操作顺序 | 线程A工作内存 | 线程B工作内存 | 指向 | 主内存 | 操作 |
|---|---|---|---|---|---|
| -- | -- | -- | -- | a = 1 | -- |
| 1 | a = 1 | -- | <-- | a = 1 | 线程A拷贝主内存变量副本 |
| 2 | a = 3 | -- | -- | a = 1 | 线程A修改工作内存变量值 |
| 3 | a = 3 | -- | --> | a = 1 | 线程A工作内存变量存储到主内存变量,主内存变量还未更新 |
| 4.1 | a = 3 | a = 1 | <-- | a = 3 | 线程B拷贝主内存变量副本随后主内存变量更新线程A工作内存变量 |
| 4.2 | a = 3 | a = 1 | <-- | a = 3 | 线程A工作内存变量存储到主内存变量随后线程B获取主内存变量副本 |
操作4的时候可能出现:1.线程A变量值还未保存到主内存变量,2.线程A变量值保存到主内存变量。使用volatile关键字解决这个问题
public static volatile int a = 1;
特性
- 保证此变量对所有线程可见,一条线程修改的值,其他线程对新值可以立即得知
- 禁止指令重排序
可见性
修改内存变量后立刻同步到主内存中,其他的线程立刻得知得益于Java的先行发生原则
先行发生原则中的volatile原则:一个volatile变量的写操作先行于后面发生的这个变量的读操作,时间顺序
定义一个静态变量:static int a = 1;
| 线程A工作内存 | 线程B工作内存 | 指向 | 主内存 | 操作 |
|---|---|---|---|---|
| -- | -- | -- | a = 1 | -- |
| a = 1 | -- | <-- | a = 1 | 线程A拷贝主内存变量副本 |
| a = 3 | -- | -- | a = 1 | 线程A修改工作内存变量值 |
| a = 3 | -- | --> | a = 1 | 线程A工作内存变量存储到主内存变量 |
| a = 3 | a = 3 | <-- | a = 3 | volatile原则:主内存变量保存线程A工作内存变量操作在线程B工作内存读取主内存变量操作之前 |
指令重排序和内存屏障
指令重排序:JVM在编译Java代码的时候或者CPU在执行JVM字节码的时候,对现有的指令进行重新排序,目的是为了再不影响最终结果的前提下,优化程序的执行效率
内存屏障:一种屏障指令,让CPU或比编译器对屏蔽指令之前和之后发出的内存操作执行一个排序约束。
编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
非线程安全
public class VolatileTest implements Runnable {
public static volatile int num;
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
num++;
}
}
public static void main(String[] args) {
for(int i = 0; i < 100; i++) {
VolatileTest t = new VolatileTest();
Thread t0 = new Thread(t);
t0.start();
}
System.out.println(num);
}
}
这段代码的结果有可能不是100000,有可能小于100000。
因为num++不是原子操作
使用原则
- 运行结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
- 变量不需要与其他的状态变量共同参与不变约束
三、原子性、可见性、有序性
原子性
一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰
原子性保障
- synchronized:monitorenter和monitorexit指令
- atomic类型:底层为native方法
- 基本数据类型(long,double非原子协定除外)
可见性
当一个线程修改了共享变量的值,其他线程立即可知
可见性保障
- volatile:先行发生(happens-before)原则
- synchronized:对一个同步块unlock之前必须把工作内存变量同步到主内存中
- final:final修饰的字段在构造器中初始化完成后,并且构造器没有把this引用传递出去,其他线程中就能看见final字段值
有序性
程序执行的顺序按照代码的先后顺序执行,在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性
有序性保障
- volatile:先行发生(happens-before)原则
- synchronized:同一个时间只能有一个线程获得锁
先行发生(happens-before)原则
JMM中两项操作之间的偏序关系,如果操作A发生于操作B之前,操作A发生的影响可以被操作B观察到
单线程和正确同步的多线程的执行结果不会被改变
规则
如果两个操作不在下列规则中,虚拟机可以对其重排序
- 程序次序规则:在一个线程内按控制流顺序
- 管程锁定规则:锁的unlock操作先发生于后面同一个锁的lock操作
- volatile变量规则:一个volatile变量的写操作先行于后面发生的这个变量的读操作
- 线程启动规则:start()先发生于此线程的每一个操作
- 线程终止规则:线程的所有操作都先发生于线程终止操作
- 线程中断规则:对线程interrupt()方法先行于被中断线程的代码检查到中断事件的发生
- 对象终结规则:一个对象初始化完成先发生于它的finalize()方法的开始
- 传递性:操作A在操作B前,操作B在操作C前,操作A一定在操作C前
并发一:Java内存模型和Volatile的更多相关文章
- 黑马-----内存模型和volatile详解
黑马程序员:Java培训.Android培训.iOS培训..Net培训 JAVA线程-内存模型和volatile详解 一.单核内存模型 1.程序运行时,将临时数据存放到Cache中 2.将CPU计算所 ...
- Java内存模型和JVM内存管理
Java内存模型和JVM内存管理 一.Java内存模型: 1.主内存和工作内存(即是本地内存): Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取 ...
- Java 内存模型和 JVM 内存结构真不是一回事
这两个概念估计有不少人会混淆,它们都可以说是 JVM 规范的一部分,但真不是一回事!它们描述和解决的是不同问题,简单来说, Java 内存模型,描述的是多线程允许的行为 JVM 内存结构,描述的是线程 ...
- JVM内存结构、Java内存模型和Java对象模型
Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点.而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚.比如本文要讨论的JVM内存结构.Java内存模型和Java对象模型 ...
- JAVA内存模型和Happens-Before规则
前言 上一篇文章王子给大家介绍了并发编程中比较关心的三个核心问题,可见性.有序性和原子性. 今天我们继续来探索并发编程的内容,聊一聊JAVA的内存模型和Happens-Before规则. JAVA内存 ...
- Java线程角度的内存模型和volatile型变量
内存模型的目标是定义程序中各个变量的访问 规则,即在虚拟机中将变量(包括实例字段,静态字段和构成数组对象的元素,不包括局部变量与方法参数,因为后者是线程私有的)存储到内存和从内存中取出变量这样的底层细 ...
- Java内存模型和ConcurrentHashMap 1.7源码分析
简介 ConcurrentHashMap 是 util.concurrent 包的重要成员.本文将结合 Java 内存模型,分析 JDK 源代码,探索 ConcurrentHashMap 高并发的具体 ...
- Java虚拟机内存模型和volatile型变量
Java虚拟机内存模型 了解Java虚拟机的内存模型,有助于我们明白为什么会发生线程安全问题. 上面这幅图是<深入理解Java虚拟机-JVM高级特性与最佳实践>的书中截图. 线程共享的变量 ...
- 【Java】JMM内存模型和JVM内存结构
JMM内存模型和JVM内存结构 JAVA内存模型(Java Memory Model) Java内存模型,一般指的是JDK 5 开始使用的新的内存模型,主要由JSR-133: JavaTM Memor ...
随机推荐
- ps/top
ps -e 显示所有进程,环境变量 -f 全格式显示 -a 显示所有用户的所有进程(包括其他用户) -u 按用户名和启动时间的顺序显示进程 -x 显示无控制终端的进程 -w 显示加宽可以显示较多信息 ...
- [USACO10HOL]赶小猪
嘟嘟嘟 这题和某一类概率题一样,大体思路都是高斯消元解方程. 不过关键还是状态得想明白.刚开始令\(f[i]\)表示炸弹在点\(i\)爆的概率,然后发现这东西根本无法转移(或者说概率本来就是\(\fr ...
- 洛谷 P3382 【模板】三分法(三分 二分)
P3382 [模板]三分法 题目提供者HansBug 难度 普及/提高- 题目描述 如题,给出一个N次函数,保证在范围[l,r]内存在一点x,使得[l,x]上单调增,[x,r]上单调减.试求出x的值. ...
- Linux下查看文件和文件夹大小 df,du命令
转自 http://www.cnblogs.com/benio/archive/2010/10/13/1849946.html df可以查看一级文件夹大小.使用比例.档案系统及其挂入点,但对文件却无能 ...
- 数据结构实验之二叉树三:统计叶子数 SDUT 3342
#include <stdio.h> #include <string.h> struct node { char data; struct node *l,*r; }; st ...
- 前端使用lodop如何获取打印状态
前面已经说过,如何简单使用lodop了,今天说一下如何获得lodop的打印状态? 在教程里面找了半天,摸索出来了一套. template: <!-- 实验代码 --> <div> ...
- Mapreduce-实现webcount代码
参考博文:https://blog.csdn.net/qq_41035588/article/details/90514824 首先安装一个Hadoop-Eclipse-Plugin 方便来对于hdf ...
- Qtcreator中printf()/fprintf()不显示问题处理方法
此处只介绍解决办法,有兴趣的朋友可以分析原因. [问题] 使用Qtcreator开发项目中,printf()的诊断信息,在“应用程序输出”窗口不显示. [解决方法] 1.printf()不显示解决示例 ...
- 线上bug或故障界定及填写规范
[线上故障与线上Bug界定] 一.线上故障: 1. 故障参照公司规范稍做调整: a) 1级故障:资讯首页或主App首页无法打开:多条业务线同时不可用:超过15分钟: b) ...
- Flask request 属性详解
Flask request 属性详解 一.关于request在Flask的官方文档中是这样介绍request的:对于 Web 应用,与客户端发送给服务器的数据交互至关重要.在 Flask 中由全局的 ...