从jmm模型漫谈到happens-befor原则
首先,代码都没有用ide敲,所以不要在意格式,能看懂就行
jmm内存模型:
jmm是什么?
jmm说白了就是定义了jvm中线程和主内存之间的抽象关系的一种模型,也就是线程之间的共享变量存储在主内存,而每个线程都拥有自己的工作内存
happens-befor原则是什么?
在说happens-befor原则之前,我们得先说说jmm的问题所在,如上述所述,每个线程都有自己的一个工作内存,那么我们以一个代码实例来看
public class Test{
int a=1;
public static void main(String []args){
for(int i=0;i<=100000;i++){
new Thread(()->{
a++;
}).start();
}
system.out.println(a);
}
}
OK,大家能看到,这里是有一个开启了100000个线程做自增操作,结果是99130,并不是预期中的100000,那么这是为什么呢?大家都知道,线程是CPU运行的最小单元,那么也就是说,多线程的情况下,cpu会去随机运行的(哪怕是设置了优先级也只是一个权重问题,无法保证强顺序
并且任务一定执行完),所以,因为我们的a++并不是一个原子操作的原因(实际上是4步),也就是说,很可能面临这种情况,当一个线程拿到了cpu的时间切片的时候,首先cpu会将a读到内存中,此时假设a=1然后弄一个临时变量,之后将临时变量增加,之后再返回结构到主内存,但如果在创建了
临时变量但还没有做自增操作的时候,cpu的时间切片突然换到了另一个线程的上面,这个时候这个线程成功做完了自增操作,此时a=2,之后cpu又切回了之前的线程,因为线程里有一个程序计数器,记录了当前线程运行到了哪行代码,所以这个时候第一个线程继续做+1操作,但此时
由于第一个线程的工作内存里的a还是1,所以这个时候线程a在+1之后还是2,之后刷到主内存,此时a=2.所以这两个线程虽然各自运行了一次a++操作,但主内存里的a其实只是加了一次而已.
那么怎么避免这种情况呢?此时就需要我们的happens-befor原则了
happens-befor原则:
1:程序在运行的时候必须按照编写的顺序一样,不能进行指令重排序,指令重排会导致什么后果呢?
public class Test{
int a = 0;
boolean b = false;
public void write(){
a=1;
b = true;
}
public void read(){
if(b){
a = a+1;
}
}
}
而指令重排后可能会是
public class Test{
int a = 0;
boolean b = false;
public void write(){
b = true;
a=1;
}
public void read(){
if(b){
a = a+1;
}
}
}
如果此时有两个线程
new Thread(->(
write();
)).start();
new Thread(->{
read();
}).stert();
假设write()方法线程肯定先于read()方法执行的情况下,此时可能会导致,在b=true的时候,read方法进入,并导致a最后=1,但我们代码的原意其实a=1优先执行的话,a=1的情况会因为read方法的bool没有变成true所以无法进入,因此指令重排已经
干扰到了我们的代码原意了.
那么在什么情况下指令不会重排呢?两种,一种是上下代码有依赖关系如:
int a = 1;
int b = a+1;
此时就不会出现重排;
还有一种就是使用大名鼎鼎的volatile关键字,它利用了内存屏障的性质保证了指令不会重排,最后来点拓展知识,就是long和double这种64字节的数据类型,在读到工作内存的时候不会原子性的,而是每次只读32字节,最后分两次读,但如果加了volatile关键字的话,那么内存屏障
能保证一次性读完64字节.
2:一个锁的解锁,必须要在这个程序的加锁之前,也就是说,我不解锁,那你就别想再加锁,保证了串行
3:对于共享数据,上一个线程对于它的修改,必须要对后续任意操作它的线程可见
4:传递性,假设有三个操作,a,b,c可以理解为a happens befor b,b happens befor c ,那么a happens befor c;
OK,volatile除了内存屏障之外,其实还有另一个作用,就是保证了可见性,它是怎么保证的呢?其实就是将工作内存给去掉,也就是让每次cpu读数据都必须要主内存里里拿,就这样保证在一个线程修改了数据后,他对所有线程都是可见的.可惜的是,这种可见性也并不能保证线程安全,因为线程安全需要两个保证,一个可见性,还有一个原子性.
假设现有一个线程1,一个线程2,一个共享变量a=1,此时线程1将a拿到工作内存做a++操作,在它还没有返回的期间线程b也拿了a到工作内存做++操作,之后不管谁先返回,a都只是做了一次操作而已.所以volatile只能保证那些赋值操作的线程安全,如:Boolean bool = true;
总而言之,volatile的作用就是在操作之间建立happens-befor的关系
最后,推荐大家看下CAS的源码,利用volatile加乐观锁,实现了不需要synchronized也能保证线程安全.
从jmm模型漫谈到happens-befor原则的更多相关文章
- JMM模型基础知识笔记
概述 内存模型可以理解为在特定的操作协议下,对特定的内存或者高速缓存进行读写访问的过程抽象,不同架构下的物理机拥有不一样的内存模型,Java虚拟机也有自己的内存模型,即Java内存模型(JavaMem ...
- Java内存模型JMM 高并发原子性可见性有序性简介 多线程中篇(十)
JVM运行时内存结构回顾 在JVM相关的介绍中,有说到JAVA运行时的内存结构,简单回顾下 整体结构如下图所示,大致分为五大块 而对于方法区中的数据,是属于所有线程共享的数据结构 而对于虚拟机栈中数据 ...
- 基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程
许多以Java多线程开发为主题的技术书籍,都会把对Java虚拟机和Java内存模型的讲解,作为讲授Java并发编程开发的主要内容,有的还深入到计算机系统的内存.CPU.缓存等予以说明.实际上,在实际的 ...
- Java并发之内存模型(JMM)浅析
背景 学习Java并发编程,JMM是绕不过的槛.在Java规范里面指出了JMM是一个比较开拓性的尝试,是一种试图定义一个一致的.跨平台的内存模型.JMM的最初目的,就是为了能够支多线程程序设计的,每个 ...
- Java内存模型(JMM)详解
在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模型.它们的答案是一致的:能够让我们更好的 ...
- Java内存模型(JMM)
JVM与线程(线程在JVM中) 1.JVM什么时候启动? 类被调用时启动,此时会启动JVM线程然后再是其他的线程(main) 2.JVM内存区域 除了程序计数器(PC)之外都有可能发生 ...
- Java原理领悟-JMM(java内存模型认知)
总线锁.缓存锁.MESI缓存一致性协议.CPU 层面的内存屏障 1.JMM定义: Java Memory Model(java内存模型)是一系列的Java虚拟机平台对开发者提供的多线程环境下的内存可见 ...
- JMM(Java内存模型)是什么?为什么使用并发?
1.计算机 首先我们需要讲解下计算机的模型:现代计算机模型是基于-冯诺依曼计算机模型 我们不用管输入和输出设备,最主要的就是中间计算器和存储器之间的交互,也就是CPU与主内存之间取数.存数. 大家会看 ...
- 并发编程之volatile与JMM多线程内存模型
一.通过程序看现象 在开始为大家讲解Java 多线程缓存模型之前,我们先看下面的这一段代码.这段代码的逻辑很简单:主线程启动了两个子线程,一个线程1.一个线程2.线程1先执行,sleep睡眠2秒钟之后 ...
随机推荐
- vscode中将本地数据push至git repository
1.新建repository 2.本地写好的代码 3.执行git init 初始化git配置文件 4.提交已暂存文件 5.填写提交信息 6.执行push命令 7.完成
- Ubuntu docker 使用命令 系列二
1.下载官方远程仓下的镜像:sudo docker pull <docker 镜像> ,sudo docker pull centos (没有指定版本,就是下载的最新的os) 2. 下载某 ...
- The lion king 经典句型摘录
What am I going to do with him? Everything the light touches is our kingdom. But I thought a king ca ...
- mac及windows下安装ss实现***
官网下载shadowsock(mac/windows都是下面地址,页面会根据当前系统自动判断所下载的包) https://sourceforge.net/projects/shadowsocksgui ...
- sql server 全部错误号详释
0 操作成功完成. 1 功能错误. 2 系统找不到指定的文件. 3 系统找不到指定的路径. 4 系统无法打开文件. 5 拒绝访问. 6 句柄无效. 7 存储控制块被损坏. 8 存储空间不足,无法处理此 ...
- vijos 1053 Easy sssp
描述 输入数据给出一个有N(2 <= N <= 1,000)个节点,M(M <= 100,000)条边的带权有向图. 要求你写一个程序, 判断这个有向图中是否存在负权回路. 如果从一 ...
- 在2015年 开发一个 Web App 必须了解的那些事
在过去的一年里,我在从头开始开发我的第一个重要的Web应用.经验教会了很多以前不知道的东西,特别是在安全性和用户体验方面. 值得一提的是,我上一次尝试构建的任何合理复杂性是在2005年.所以,在安全防 ...
- scriptPubKey and scriptSig
First of all two matching scripts are used in two different transactions, one that transfers funds t ...
- lodash中文说明文档
lodash中文说明文档 https://www.css88.com/doc/lodash/
- linux部署全流程(未完)
一.环境搭建 1.jdk 2.tomcat 3.nginx 4.redis 推荐工具:winSCP(用来传输文件).SecureCRT(用来执行命令) 1.jdk 下载地址:https://www.o ...