java内存模型中工作内存并不一定会同步主内存的情况分析
其实是为了填之前的一个坑 在一个多线程的案例中出现了阻塞的情况。 https://www.cnblogs.com/hetutu-5238/p/10477875.html 其中的第二个问题,即多个线程循环顺序打印1,2,3,4
public class Demo2 {
public static volatile int index = 0;
public static void main(String[] args) {
ThreadTest t1 = new ThreadTest("线程1" , 0);
ThreadTest t2 = new ThreadTest("线程2" , 1);
ThreadTest t3 = new ThreadTest("线程3" , 2);
ThreadTest t4 = new ThreadTest("线程4" , 3);
t1.start();
t2.start();
t3.start();
t4.start();
}
static class ThreadTest extends Thread {
private int i;
public ThreadTest(String name , int i) {
super(name);
this.i = i;
}
@Override
public void run() {
while ( true ) {
if ( (index & 3) == i ) {
System.out.println(Thread.currentThread().getName() + ":" + (i+1));
index++;
}
}
}
}
}
这儿的如果我们把index值的volatile 修饰符去掉,会造成程序卡死的问题。这儿我们会发现并没有死锁的产生条件。打印线程信息后

会发现并没有死锁,而是所有的线程阻塞在了第54行,也就是代码中的if ( (index & 3) == i ),这就很奇怪了,尽管我们没有加volatile关键字。但按理说也只是影响工作内存读取主内存index的值的时间而已,一旦同步了最新的值以后,必定有一个线程能够打印出信息才对。
所以我改造了下程序,每个线程保存一个index值的成员变量,然后我们在几秒钟后打印每个线程的index值,看下具体值的情况。改造结果如下
public class Demo {
public static int index = 0;
public static void main(String[] args) {
ThreadTest t1 = new ThreadTest("线程1" , 0);
ThreadTest t2 = new ThreadTest("线程2" , 1);
ThreadTest t3 = new ThreadTest("线程3" , 2);
ThreadTest t4 = new ThreadTest("线程4" , 3);
t1.start();
t2.start();
t3.start();
t4.start();
ScheduledExecutorService sh = Executors.newSingleThreadScheduledExecutor();
sh.scheduleWithFixedDelay(()->{
System.out.println("当前主内存的index:"+index);
System.out.println("t1中的index为"+t1.getK());
System.out.println("t2中的index为"+t2.getK());
System.out.println("t3中的index为"+t4.getK());
System.out.println("t4中的index为"+t4.getK());
},5,5, TimeUnit.SECONDS);
}
static class ThreadTest extends Thread {
private int i;
private int k;
public ThreadTest(String name , int i) {
super(name);
this.i = i;
}
public int getK() {
return k;
}
@Override
public void run() {
while ( true ) {
k = index;
if ( (index & 3) == i ) {
System.out.println(Thread.currentThread().getName() +":"+index+ ":" + (i));
index++;
}
}
}
}
}
然后运行结果控制台打印如下

发现index增加到372时就不在增加了,所以线程处于阻塞状态。按理说372 &3 = 0 ,应该由线程1来执行打印语句,这儿的结果t1的index值为369,并没有同步到最新的值。这儿注意每隔5秒打印一次,
而我们的线程的循环中是一直在将index赋给线程的k,说明在循环的10秒里主内存的值都还没有同步到线程的工作内存中。而369并不满足 & 3 = 0 的判断条件,其他线程虽然同步到了最新的值但是自身并不满足判断条件,所以线程就一直处于循环状态。
到目前可以说已经找到了问题所在了,即工作内存在实验的时间里一直都没有同步主内存的值。而为什么volatile关键字加上就可以呢,这个很好解释,因为这个关键字除了防止指令重排序外,还有的一个重要作用就是保持内存可见性。即volatile修饰的变量 保证某个线程对该变量执行use操作的前一步必须执行load操作加载主内存的值,保证了一定能看到其他线程推送到主内存的最新的值。同时也规定了assign操作后必须立即执行store操作,保证了自己的修改一定能被其他的线程看到。
所以我们接下来的问题则是为何上面示例中的代码会一直不同步主内存的值了。网上找了下,这个问题在The Java® Language Specification Java SE 7 Edition这本书中的第17.3节中刚好有对类似问题的提及
地址:https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html

上面圈红的线则是说Thread.sleep或者yield操作并没有同步语义操作,所以并不一定会在调用前将写操作同步到主内存中,而调用后也不会一定会刷新工作内存寄存器中缓存的值。
下面则是说明这个this.done只会读一次,而其他循环的时候则会一直从工作内存中的缓存值中读取,即便我们在其他线程中修改了this.done的值,该线程也不会获取到。
可以写个应用程序实验一下
public class Demo2 {
public static boolean index = true;
public static int k = 1;
public static void main(String[] args) {
ThreadTest t1 = new ThreadTest("线程1" , 0);
ThreadTest t2 = new ThreadTest("线程2" , 1);
ThreadTest t3 = new ThreadTest("线程3" , 2);
ThreadTest t4 = new ThreadTest("线程4" , 3);
t1.start();
t2.start();
t3.start();
t4.start();
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.schedule(()->{
index = false;
},3,TimeUnit.SECONDS);
}
static class ThreadTest extends Thread {
private int i;
public ThreadTest(String name , int i) {
super(name);
this.i = i;
}
@Override
public void run() {
while ( index ) {
k++;
}
System.out.println(Thread.currentThread().getName()+"跳出循环");
}
}
}
上述代码在我的环境中windows+oracle jdk8运行了三次,等待20秒,均没有一个线程打印出 “跳出循环”。确实说明四个线程都是第一次读取到index值之后,就一直没有主动去同步主内存的值,而是一直拿的工作内存中缓存的值。不过在我的代码中实验了多次,在while循环中使用Thread.sleep则每次都会跳出循环,所以我用一个int值k来代替,不知道是否是java8在这块有做什么改动,但我们根据上面的官方文档中以及之前对valotale关键字的总结可以得知,至少这两种情况工作内存是一定会主动去同步主内存的值的:1.如果在线程中使用了valotale关键字修饰变量那么会保证在用到该变量之前从主内存同步值。2.如果线程运行中使用了同步语义也会保证从主内存中同步值。 我们可以实验一下
1.我们可以实验下只是将K值的修饰符添加volatile ,并不改变index的值,经过我的多次实验,发现确实所有线程都会跳出循环。

2.或者我们可以在循环中添加同步语义也可以让工作内存同步主内存的值,将run中的方法改为如下,会发现如果不添加synchronized块是发生不会跳出循环的情况的。
public void run() {
while ( index ) {
Object o = new Object();
synchronized (o){
}
}
System.out.println(Thread.currentThread().getName()+"跳出循环");
}
多次实验结果均是如此,说明这两种情况确实会主动同步。
至此这个坑算是终于填了。总结起来就是一句话:工作内存有可能会一直使用缓存的值而不会主动同步主内存的值,目前可以保证的是工作内存中如果有使用volatile修饰的变量或者显式的同步语义,是一定会主动同步主内存的值的,当然可能还有待探究的其他情况也会主动同步。
java内存模型中工作内存并不一定会同步主内存的情况分析的更多相关文章
- 什么是Java内存模型中的happens-before
Java内存模型JMM Java内存模型(即Java Memory Model , 简称JMM),本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序个各个变量(包括实 ...
- (第三章)Java内存模型(中)
一.volatile的内存语义 1.1 volatile的特性 理解volatile特性的一个好办法是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步.下面通过具体 ...
- 深入理解Java内存模型中的虚拟机栈
深入理解Java内存模型中的虚拟机栈 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都会有各自的用途,以及创建和销毁的时间,有的区域会随着虚拟机进程的启 ...
- Java内存模型-volatile的内存语义
一 引言 听说在Java 5之前volatile关键字备受争议,所以本文也不讨论1.5版本之前的volatile.本文主要针对1.5后即JSR-133针对volatile做了强化后的了解. 二 vol ...
- Java内存模型中volatile关键字的作用
volatile作用总结: 1. 强制线程从公共内存中取得变量的值,而不是从线程的私有的本地内存中,volatile修饰的变量不具有原子性(修改一个变量的值不能同步). 2. 保证volatile修饰 ...
- Java 执行过程中的内存模型
一.前言 本文的主要工作:尝试以时间顺序追踪一遍 Java 执行的整个过程,以及展示 JVM 中内存模型的相应变化. 本文的主要目的:希望能够通过 Java 执行过程的冰山一角,增进对编程语言工作原理 ...
- [转载]Java应用程序中的内存泄漏及内存管理
近期发现测试的项目中有JAVA内存泄露的现象.虽然JAVA有垃圾回收的机制,但是如果不及时释放引用就会发生内存泄露现象.在实际工作中我们使用Jprofiler调用java自带的 jmap来做检测还是很 ...
- Java 内存模型和硬件内存架构笔记
前言 可跟<主存存取和磁盘存取原理笔记>串着看 https://blog.csdn.net/suifeng3051/article/details/52611310 杂技 Java 内存模 ...
- 【java】java内存模型(2)--volatile内存语义详解
多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”.可见性的意思是当一个线程 ...
随机推荐
- [SDOI2016] 生成魔咒 - 后缀数组,平衡树,STL,时间倒流
[SDOI2016] 生成魔咒 Description 初态串为空,每次在末尾追加一个字符,动态维护本质不同的子串数. Solution 考虑时间倒流,并将串反转,则变为每次从开头删掉一个字符,即每次 ...
- 从原理到方案,一步步讲解web移动端实现自适应等比缩放
前言 在移动端做自适应,我们常用的有媒体查询,rem ,em,宽度百分比这几种方案.但是都各有其缺点. 首先拿媒体查询来说,在某一个宽度区间内只能使用一种样式,为了适应不同屏幕要,css的代码量就会增 ...
- IDA pro 6.8显示中文字符串的方法
IDA pro 6.8设置显示中文字符串的方法 M4x原创,转载请表明出处http://www.cnblogs.com/WangAoBo/p/7636335.html IDA是一款强大无比的反编译软件 ...
- 工具系列 | git checkout 可替换命令 git switch 和 git restore
前言 git checkout 这个命令承担了太多职责,既被用来切换分支,又被用来恢复工作区文件,对用户造成了很大的认知负担. Git社区发布了Git的新版本2.23.在该版本中,有一个特性非常引人瞩 ...
- 剑指offer 面试题 删除链表中重复的节点
题目描述 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针. 例如,链表1->2->3->3->4->4->5 处理后 ...
- JS高级---函数作为返回值使用
函数作为返回值使用 function f1() { console.log("f1函数开始"); return function () { console.log("函数 ...
- OI记录
这里是蒟蒻xsl的OI记录. 2017 2017.03.?? 开始接触OI 2017.10.14 参加NOIP2017普及组初赛,踩着分数线进入了复赛 2017.11.11 参加NOIP2017普及组 ...
- 解决报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: TLS handshaketimeout
报错: [root@localhost /]# sudo docker pull ubuntuError response from daemon: Get https://registry-1.do ...
- chrome firefox浏览器屏蔽百度热搜
我是原文 操作 点击拦截元素,然后选择页面元素,添加
- pudn免费下载账号 codeforge积分账号 pudn共享账号 codeforge下载账号
www.pudn.com和www.codeforge.cn网站下载代码很好,没有积分怎么办?那么多好的matlab代码,matlab程序,C,JAVA等等,都要充值啊!!! 下面的账号积分都用完了,大 ...