浅析JMM线程工作内存什么时候读取主存变量
以下全是查看其它博客,和自己的猜想结论,欢迎改正。
- 猜想1:
释放CPU时间片后再次抢夺成功
会将主存的变量刷回线程工作内存。
private static int num = 10;
// 开3个线程循环读num变量的值, 当num < 1线程就结束
for (int i = 0; i < 3; i++) {
new Thread(() -> {
while (true) {
if (num < 1) return;
// 等待200毫秒
// try { TimeUnit.MILLISECONDS.sleep(1); }
// catch (InterruptedException e) { e.printStackTrace(); }
Thread.yield();
}
}, String.valueOf(i)).start();
}
TimeUnit.MILLISECONDS.sleep(200);
for (int i = 0; i < 100000; i++) { num--; }
System.out.println("最终: num = " + num);
// 输出:
最终: num = -99990
上面代码线程会退出,说明num变量在线程工作内存中读取到了的主存变量的值。
如果没有Thread.yield()
、TimeUnit.MILLISECONDS.sleep()
线程将会一直死循环,因为线程工作内存没有读取主存变量。解决办法:
volatile
、释放CPU时间片的操作: sleep、yield等
、synchronize、Lock等同步
- 猜想2:
修改线程工作内存中的值
也会将主存的变量刷回线程工作内存。
private static int num = 10;
// 开3个线程循环读num变量的值, 当num < 1线程就结束
for (int i = 0; i < 3; i++) {
new Thread(() -> {
while (true) {
if (num < 1) return;
num--;
}
}, String.valueOf(i)).start();
}
TimeUnit.MILLISECONDS.sleep(200);
System.out.println("最终: num = " + num);
// 输出结果:
2 => 9
0 => 9
1 => 9
2 => 8
1 => 8
0 => 8
2 => 7
0 => 6
1 => 6
0 => 4
1 => 4
2 => 4
1 => 3
0 => 2
2 => 3
0 => 1
1 => 1
2 => 1
1 => 0
0 => 0
2 => -1
最终: num = -1
上面代码同样的也是可以退出循环的
我的猜想:num--
前同步了主存中的变量并且修改值后再同步到主存中。
- 猜想2扩展:同一个线程读取到相同的值是因为
num
不是原子性的,看似只有num--
这一步,其实反编译成字节码后是多步的,无法保证原子性,使用Atomicxx
可以解决变量的原子性,当然同步方法也是保证原子性的保证的是代码块的或方法的原子性。
private static AtomicInteger num1 = new AtomicInteger(10);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
while (true) {
if (num1.get() < 1) return;
// 睡眠
try { Thread.sleep(50); }
catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + " => " + num1.getAndDecrement());
}
}, String.valueOf(i)).start();
}
TimeUnit.SECONDS.sleep(2);
System.out.println("最终: num1 = " + num1);
// 输出:
1 => 10
0 => 9
2 => 8
0 => 7
1 => 6
2 => 5
0 => 3
1 => 4
2 => 2
1 => 1
0 => 0
2 => -1
最终: num1 = -2
减到小于0以下就不用多说了,因为并发的原因,以两个线程为例。
可能线程1
执行到睡眠等待,线程2
执行完了修改值,num1 = 0
,此时线程1
睡眠完毕,开始继续往后执行--操作
,此时num1 = -1
。
- 总结
如果想线程能拿到最新的主存变量的值,建议在变量上加上volatile
关键字,因为volatile三大原则:保证可见性
、不保证原子性
、禁止指令重排:内存屏障
。所以想用volatile还想用原子性,就使用JUC下的AtomicXxxx
类。