求助>一道面试题引发的对Java内存模型的一点疑问,第二部。>
12回复
7月前

一道面试题引发的对Java内存模型的一点疑问,第二部。



环境信息如下:
JDK版本:1.8.0_251
JDK版本.png

java.vm.version=25.251-b08
java.vm.vendor=Oracle Corporation
java.vm.name=Java HotSpot™ 64-Bit Server VM
java.vm.specification.name=Java Virtual Machine Specification
java.runtime.version=1.8.0_251-b08
os.arch=amd64
sun.management.compiler=HotSpot 64-Bit Tiered Compilers
java.vm.info=mixed mode

操作系统信息:
电脑版本.png

原问题
基于原来的问题,我又发现一个很奇怪的现象。

第一个问题:
如图
问题.png

第二个问题:
按照我的理解,这个程序如果Main线程在num=0时候先执行了while()循环,然后子线程再对num进行加1操作,肯定会出现死循环的情况。因为JMM规定普通的共享变量存在于主内存当中,然后每个线程都有自己的工作内存,每个线程用到变量的时候会先从主存中复制一份到自己的工作内存。就你这程序来说,如果Main线程在num=0时候先执行了while()循环,这个时候Main线程会把num的值复制一份到自己的工作内存,然后Main线程的while循环就只会读线程工作内存的副本。接下来子线程开始运行,子线程先把num的值复制一份到子线程的工作内存里面,然后对num进行加1操作,然后子线程运行结束把num刷新到主存里面。注意,即使此时子线程把num的最新值刷新到主存中,Main线程的while循环也不会结束,因为Main线程只会读自己的工作内存里面num的值,Main线程的工作内存里面的num现在还是0啊。
你的num没有用volatile修饰,所以Main线程里面的num不会失效。
(请大顺便解释一下我这个疑惑,就上面加粗这行的疑惑。)
所以,我觉得你这个程序只要Main线程在num=0时候先执行了while()循环,然后子线程再对num进行加1操作,即使没有jit的激进编译,也肯定会出现死循环的情况。

不过我发现我上面的说法也不对。我发现加了-Xint参数之后,就肯定不会出现死循环了。这是为什么呢?难道加了-Xint参数之后,Main线程每次循环的时候都会去主存中读取num的值吗?这跟JMM规定的不太一样啊?JMM规定普通的共享变量存在于主内存当中,然后每个线程都有自己的工作内存。

5034 阅读
请先登录,再评论

回复列表

https://a.perfma.net/img/2382850
burnbrid1月前

我感觉大神: 空无H 的这篇文章一个 println 竟然比 volatile 还好使?彻底解决了我所有的疑问了。

3
昕雨2周前

print的时候会有synchronized方法,引发指令重排序,被其他线程抢到资源

https://a.perfma.net/img/2382850
burnbrid1月前

知乎R大(RednaxelaFX) 的回答 下面的代码 Java 线程结束原因是什么?

打印函数是有副作用的,加上之后循环里的num不会被优化成标量,加上-XX:-UseOnStackReplacement你再试试

就如你说的main线程先运行了while循环,复制到自己main工作内存,num=0,进入循环。当执行 System.out.println()的时候,这个System的print方法底层是加了synchronized锁的。加锁的时候,会保证可见性的,将工作内存清空,从主内存读取最新值。如果,没加锁前,子进程执行了,修改了num的值。就会造成num不等于0,main线程的工作内存获取最新的num值不等于0,就不会执行while循环了。 我个人理解是这样的。

burnbrid7月前
回复 这是给你机会,向��🏃:

总感觉这答案都不太靠谱,笨神能来回答一下就好了。

回复
回复 burnbrid:

1.num是没有在同步的代码块中。但是同步的时候,JVM 会尽力保证内存的可见性,即便这个变量没有被同步关键字修饰.
2. 同步过程中,CPU空闲时间比较多就有可能有时间去保证内存的可见性,这样也会使得num值被main线程同步最新主内存的值。
你发的那篇https://blog.csdn.net/C_AJing/article/details/103307797讲的挺正确的。

回复
burnbrid7月前
回复 这是给你机会,向👆🏃:

1、num这个变量是在System.out.println()外面的,println这个方法加锁synchronized跟num这个变量应该没有关系。感觉你说的不太对。
2、如果,没加锁前,子进程执行了,修改了num的值。就会造成num不等于0,main线程的工作内存获取最新的num值不等于0,就不会执行while循环了。 我个人理解是这样的。你这句话我没理解是什么意思。

回复
浑水7月前

先码一下,坐等大神😓

https://a.perfma.net/img/2382850
burnbrid7月前

原问题
原问题里面采纳的答案说跟JIT激进编译有关系。不过我个人认为绝对跟JIT的激进编译没有关系,就是num变量的可见性问题,JMM的规范:每个线程都有自己的工作内存。
第一:原问题里面说的JDK7、JDK8均已试验,均能偶然触发。为啥偶然触发呢?是因为一旦Main线程在num=0时候先执行了while()循环,然后子线程再对num进行加1操作,就会出现死循环的情况。你说的偶然就看Main线程跟子线程谁先运行了。就像你评论里面回复别人的 ‘在num++前面加行Thread.sleep(1000)。’,你发现没?在num++前面加了Thread.sleep(1000)就百分之百会出现死循环,这是为啥呢?就是因为Main线程在num=0时候先执行了while()循环,然后子线程再对num进行加1操作,就会出现死循环的情况。
第二:原问题下面的评论还说加了-Xint、-XX:-UseOnStackReplacement参数,问题就不会出现了。为啥加了-Xint、-XX:-UseOnStackReplacement参数,问题就不会出现了?这个原问题下面评论里面的人已经说出来了,加了-Xint解释执行情况下getstatic指令是从内存读取值的,所以Main线程每次循环的时候都是从主内存读值的,子线程修改了num的值,Main线程可以读到,所以加了-Xint解释执行肯定不会出现死循环。

1