从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秒钟之后 ...
随机推荐
- 【转】10种简单的Java性能优化
10种简单的Java性能优化 2015/06/23 | 分类: 基础技术 | 14 条评论 | 标签: 性能优化 分享到: 本文由 ImportNew - 一直在路上 翻译自 jaxenter.欢迎加 ...
- Oracle EXPDP and IMPDP
一.特点 • 可通过 DBMS_DATAPUMP 调用 • 可提供以下工具: – expdp – impdp – 基于 Web 的界面 • 提供四种数据移动方法: – 数据文件复制 – 直接路径 – ...
- python+selenium(环境的安装)
前言:网上的资料层次不齐,且资料也不全,容易误导新手,所以笔者愿意把你的知识免费分享给大家,笔者用的版本为:python3 此时可能新手就会问了,为什么不用python2呢,因为道理很简单,人要往前走 ...
- 如何破解密码的哈希值,破解双MD5密码值
这是关于我如何破解密码的哈希值1亿2200万* John the Ripper和oclHashcat-plus故事. 这是几个月前,当我看到一条推特:从korelogic约含共1亿4600万个密码的密 ...
- python实战教程之自动扫雷(自己存下来学习之用)
3.python的第三方库win32api,win32gui,win32con,Pillow,numpy,opencv可通过 pip install --upgrade SomePackage 来进行 ...
- numpy次方计算
>>> 2**np.arange(3, 6) array([ 8, 16, 32])
- QT_1
QT概述 1.1 QT 是一个跨平台的C++图形用户界面应用程序框架 1.2 发展史: 1991奇趣科技 1.3 QT 版本:商业版.开源版 1.4 优点: 1.4.1 跨平台 1.4.2 接口简单 ...
- PHP23 AJAX分页
模型代码设计 以留言信息管理为例. 获取根据条件查询记录总数和分页数据. <?php namespace application\admin\models; use core\mybase\Mo ...
- win手动编译JAVA 未完成(系统path未加入文章)
java 下面存.BAT dir /s /B *.java > sources.txtjavac @sources.txt -bootclasspath "C:\Users\88797 ...
- java session cookie的使用
Cookie; Session; URL重写; cookie在J2EE项目中的使用,Java中把Cookie封装成了java.servlet.http.Cookie类.每个Cookie都是该Cooki ...