Java线程内存模型-JVM-底层原理

public class Demo1 {
private static boolean initFlag=false;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("waiting data......");
while (!initFlag)
{
}
System.out.println("====================");
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
prepareData();
}
}).start();
}
public static void prepareData()
{
System.out.println("prepare data.....");
initFlag=true;
System.out.println("prepare data end....");
}
}
运行结果如下:线程1未结束,但打印的数据可能与你想的不一样,我们一般会这样想:当线程1由于while(! initFlag)而陷入死循环,则线程1就一直停在那里,然后线程2在运行时将initFlag的值改为true,那么while(! initFlag)不应该不满足条件而跳出吗,然后不应该把system.out.prinltln("=============")打印出来吗
其实这样想是不对的,当理解了底层原理之后你就明白了

分析如下:
一、首先先了解几个JVM数据原子操作
*read(读取):从主内存中读取数据
*load(载入):将内存读取到的数据写入工作内存
*use(使用):从工作内存读取数据来计算
*assign(赋值):将计算好的值重新赋值给工作内存中的变量
*store(存储):将工作内存数据写入到主内存
*write(写入):将store过去的变量值赋值给主内存中的变量
*lock(锁定):将主内存变量加锁,标识为线程独占状态
*unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量
二、
底层原理:
Java线程线程内存模型和cpu缓存模型类似;
1、代码储存在主内存,然后运行程序,线程启动,之后JVM通过read将initFlag变量读取出来,再通过load将initFlag=false加载到工作内存中的共享变量副本中
2、cpu 调用数据来计算,由于线程1运行的是while(!initFlag),则陷入循环,线程1就一直停在那个地方
3、接着看线程2,由于从主内存到工作内存一致,就不再多说,然后cpu会调用数据来计算,由于线程2的计算是将initFlag变为true,则cpu会调用assign将计算好的initFlag=true重新赋值给工作内存中的变量
4.线程2由于工作内存数据改变,则将通过store将数据写入到主内存中,但此时主内存中的变量值还未改变,还要通过write将store过去的变量值赋值给主内存中的变量,此时程序基本结束
三、但是这样的话线程1就不会结束,就不会实现并发,那么该怎么操作呢。
这时我们要在代码中添加volatile关键字,也就是代码开头类的属性private static volatile boolean initFlag=false;当加入了volatile关键字之后,结果如下

这说明了线程1结束了,也就是线程1中的initFlag的值变为了true,这是什么原因呢?
其实源于总线(MESI缓存一致性协议),当线程2工作内存中的共享变量变为true时,由于volatile的存在,原子操作中的lock会迅速将该变量通过store和write写入到主内存,此时主内存中的initFlag的值已经变为true,然后我们要来理解一下MESI缓存一致性协议:就是cpu使用总线嗅探机制对使用store经过总线的数据进行监视(总线就是Java线程内存模型中绿色包括的,总线相当于你打开台式计算机的后盖,然后会发现一排一排的线,然后它连接着cpu、主内存等,当然cpu上的主板也存在),线程2:lock锁将initFlag=true使用store写入主内存时,这时使用了store并经过总线,此时线程1,通过cpu总线嗅探机制以及发现initFlag发生改变,则会使线程1中的工作内存中的变量失效,当失效之后,就会重新从主内存中读取数据,之后cpu再use工作内存中的initFlag=true数据,再则cpu计算while(!initFlag)时就会跳出循环,然后线程1就会结束,此时system.out.println("============")就会打印出来。
至此,volatile的功能就显而易见了:调用lock锁迅速将改变的数据写入到主内存中,其间使用了store,则会启动MESI缓存一致性协议(使用cpu嗅探机制对使用store通过总线的数据进行监视)
四、然后你是否会有这样的疑问:如果我不使用volatile关键字,那线程2的工作内存中的数据改变之后依旧会使用store和write写入到主内存中呀,依旧经过了总线,那么volatile还有什么用,lock锁还有什么用呢,直接去掉不就好了吗?当然我们来看一下原因:
原因1:如果不使用volatile中的lock,那么在你线程2工作内存中的数据改变时,它不一定会迅速将改变的数据写入到主内存中,如果这个线程2还有其他复杂的步骤,那么肯定会有很大影响。
原因2:如果没有lock,那么当你将改变线程2中工作内存中的数据时,使用store通过了总线,此时cpu嗅探机制发现数据改变了,然后就会令线程1中工作内存中的数据失效,然后重新从主内存中读取,就在此时问题来了,你线程2没有迅速将改变的数据写入到主内存中,我们知道主内存中的数据改变要经过store和write,如果线程1在你线程2将数据赋值给主内存之前就已经重新读取了数据,那么此时读取的还是initFlag=false,那么线程2依旧还是没有停下来,这只是两个线程,如果是很多线程,那么就可能会引发并发问题。或者是两个线程都改变数据,那么如果不加使用lock前缀指令,那同时写入主内存就会引发并发问题
五:注意
以前的计算机是利用lock锁将线程上锁,必须等待这个线程结束后,然后其他线程才能继续运行,然后你会发现这将并发变成了一串串,这样的效率肯定很低
而以上我所说的lock锁的迅速是为了方便理解,它是将数据从store->write->主内存上锁,所以上锁的只是给主内存赋值过程,由于主内存变量的赋值超每秒可达上百万次,所以这个时间可以忽略不记
Java线程内存模型-JVM-底层原理的更多相关文章
- java线程内存模型,线程、工作内存、主内存
转自:http://rainyear.iteye.com/blog/1734311 java线程内存模型 线程.工作内存.主内存三者之间的交互关系图: key edeas 所有线程共享主内存 每个线程 ...
- Java I/O模型及其底层原理
Java I/O是Java基础之一,在面试中也比较常见,在这里我们尝试通过这篇文章阐述Java I/O的基础概念,帮助大家更好的理解Java I/O. 在刚开始学习Java I/O时,我很迷惑,因为网 ...
- 【Java并发】1. Java线程内存模型JMM及volatile相关知识
Java招聘知识合集:https://www.cnblogs.com/spzmmd/tag/Java招聘知识合集/ 该系列用于汇集Java招聘需要的知识点 JMM 并发编程的三大特性:可见性(vola ...
- 【转】Java 内存模型及GC原理
一个优秀Java程序员,必须了解Java内存模型.GC工作原理,以及如何优化GC的性能.与GC进行有限的交互,有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只有全面提升内存的管理效率,才能 ...
- Java虚拟机--内存模型与线程
Java虚拟机--内存模型与线程 高速缓存:处理器要与内存交互,如读取.存储运算结果,而计算机的存储设备和处理器的运算速度差异巨大,所以加入一层读写速度和处理器接近的高速缓存来作为内存和处理器之间的缓 ...
- Java 内存模型及GC原理 (转载)
一个优秀Java程序员,必须了解Java内存模型.GC工作原理,以及如何优化GC的性能.与GC进行有限的交互,有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只有全面提升内存的管理效率,才能 ...
- Java 内存模型、GC原理及算法
Java 内存模型.GC原理:https://blog.csdn.net/ithomer/article/details/6252552 GC算法:https://www.cnblogs.com/sm ...
- JVM底层原理及调优之笔记一
JVM底层原理及调优 1.java虚拟机内存模型(JVM内存模型) 1.堆(-Xms -Xmx -Xmn) java堆,也称为GC堆,是JVM中所管理的内存中最大的一块内存区域,是线程共享的,在JVM ...
- 全网最硬核 Java 新内存模型解析与实验单篇版(不断更新QA中)
个人创作公约:本人声明创作的所有文章皆为自己原创,如果有参考任何文章的地方,会标注出来,如果有疏漏,欢迎大家批判.如果大家发现网上有抄袭本文章的,欢迎举报,并且积极向这个 github 仓库 提交 i ...
随机推荐
- VS Code配置Python环境
Visual Studio Code配置Python环境 目录 Visual Studio Code配置Python环境 1.安装Python环境 2.安装VS Code 2.1 下载 2.2 配置中 ...
- linux下oracle数据库的启动
linux下oracle数据库的启动 一.切换oracle用户 命令:su - oracle 二.运行sqlplus命令,进入sqlplus环境 命令:sqlplus /nolog (nolog参数表 ...
- 论文解读(GMI)《Graph Representation Learning via Graphical Mutual Information Maximization》2
Paper Information 论文作者:Zhen Peng.Wenbing Huang.Minnan Luo.Q. Zheng.Yu Rong.Tingyang Xu.Junzhou Huang ...
- 一文带你了解Lakehouse的并发控制:我们是否过于乐观
1. 概述 如今数据湖上的事务被认为是 Lakehouse 的一个关键特征. 但到目前为止,实际完成了什么? 目前有哪些方法? 它们在现实世界中的表现如何? 这些问题是本博客的重点. 有幸从事过各种数 ...
- dfs:x+y=z
#include <iostream.h> int a[100]; static int stat=0; void dfs(int n) { if(n==3) { if(a[0]+a[1] ...
- 你的图片可能是这样被CORB“拦截”的
问题 最近学习一个uniapp+nodejs的项目,前端写了这样一个标签 <image :src="info.imgUrl" ></image> 按理说不应 ...
- 在Spring框架中如何更有效地使用JDBC?
使用SpringJDBC 框架,资源管理和错误处理的代价都会被减轻.所以开发者只需写statements 和 queries从数据存取数据,JDBC也可以在Spring框架提供的模板类的帮助下更有效地 ...
- 描述一下 DispatcherServlet 的工作流程 ?
DispatcherServlet 的工作流程可以用一幅图来说明: 1.向服务器发送 HTTP 请求,请求被前端控制器 DispatcherServlet 捕获. 2. DispatcherServl ...
- js--事件循环机制
前言 我们知道JavaScript 是单线程的编程语言,只能同一时间内做一件事,按顺序来处理事件,但是在遇到异步事件的时候,js线程并没有阻塞,还会继续执行,这又是为什么呢?本文来总结一下js 的事件 ...
- 插值方法 - Lagrange插值多项式
Lagrange插值多项式代码: 1 # -*- coding: utf-8 -*- 2 """ 3 Created on Wed Mar 25 15:43:42 202 ...