神出鬼没java的println方法,导致的问题
网友给出的 迷幻问题一 没有println方法,主线程无法读变量
public class ThreadTest {
public static void main(String[] args) {
Num num = new Num(0);
new Thread(()->{
while (true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num.setI(num.getI()+1);
}
}).start();
//问题代码
// while (true){
// //System.out.println注释掉 就不能打印到3s了..为什么?
// //System.out.println(num.getI());
// if (num.getI()==3) {
// System.out.println("到3s了!!!!");
// break;
// }
// }
//解决代码
while (true){
synchronized (num){
if (num.getI()==3) {
System.out.println("到3s了!!!!");
break;
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class Num{
private int i;
public Num(int i) {
this.i = i;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
}
先看下println方法
public void println(String x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
再说问题:
如果删掉解决代码,实际上num并没有锁,那么为什么在问题代码中不能读到想要的值然后顺利的输出?
第一轮测试,认为是锁的问题,但没有解决。
第二轮测试,认为是锁的持有和释放过程会产生时间差,所以主线程(main)加了线程等待。目标达成了,但还是有疑惑,因为当println() 任意字符串的时候,也是能正常达到目标的。
作为能手撸JVM的大佬,肯定不会就不能这么轻易放过这个问题。
想起CPU的内存和JVM的内存模型的对应关系,哪是不是JVM的工作内存并没有把A线程修改的num变量的值,更新到主内存,或者说B线程没有读到num变量在主内存的最新值呢?
那如何确定JVM是把一个变量是真的把主内存的num拷贝到工作内存呢?
如果JVM拷贝了num变量到A线程和B线程,那拷贝的是变量的副本,还是这个变量的地址呢?
带着这些疑惑重新看了一遍代码,发现num的值是int基本类型,注意,他是值。。而不是Integer类型,所以,JVM是不是把int值直接拷贝到了A B线程,然后在工作内存中修改后,导致 A B 线程不能及时更新数据?
我把num值改成500(让num的值成为引用类型),然后在主线程main的while循环中这样写
if (num.getI()==520) {
System.out.println("到3s了!!!!");
break;
}
结果目标还是没有达成,由此可见,就算是引用类型的数据,A B线程拿到的也只是这个变量的地址,根据我写JVM的经验,这种操作,拿到变量地址,再工作内存中计算好结果,才会写主内存,而不是拿着地址,直接修改值。
但网友又问了,如果不是直接修改?那为什么打断点的时候就直接能看到最新值?这个和工作内存与主内存同步有关,后面再讲。
得想办法实现可见性才行!不想用JAVA的关键字,还有什么办法?
如果我自己实现JVM,除了关键字,可以提供哪些办法实现内存可见性?顺着这个思路理出:
加锁:因为加锁,就要确保加锁代码块中的变量是最新的,
while (true){
synchronized (ThreadTest.class){
}
if (num.getI()==3) {
System.out.println("到3s了!!!!");
break;
}
}
目标达成!注意,因为我只需要一个更新主线程工作内存的动作就行,所以给谁加锁无所谓...
还有没有其他方法?试试线程的睡眠和唤醒。
Thread.sleep(100);
测试后,都能达成目标。
线程休眠,加锁,都能刷新(提交工作内存的改动和读取主内存的最新数据)工作内存。
那刚才提到的断点是不是可以把主内存的数据刷到工作内存?根据写调式器的经验,如果开发者下了断点,一般情况下都会把最新数据刷给调试器。
回到问题本身
println中使用了加锁的操作,导致调用println的线程(也就是这个main)更新了主内存的数据到工作内存..
java新手自学群 626070845
java/springboot/hadoop/JVM 群 4915800
Hadoop/mongodb(搭建/开发/运维)Q群481975850
GOLang Q1群:6848027
GOLang Q2群:450509103
GOLang Q3群:436173132
GOLang Q4群:141984758
GOLang Q5群:215535604
C/C++/QT群 1414577
单片机嵌入式/电子电路入门群群 306312845
MUD/LIB/交流群 391486684
Electron/koa/Nodejs/express 214737701
大前端群vue/js/ts 165150391
操作系统研发群:15375777
汇编/辅助/破解新手群:755783453
大数据 elasticsearch 群 481975850
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。