浅析JMM线程工作内存什么时候读取主存变量

浅析JMM线程工作内存什么时候读取主存变量

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类。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值