1、背景
小皮和小芳为了日后的结婚准备每天都有往结婚基金里面存钱,最近终于是存够了10万,不过他俩都没有每天去银行查账的习惯。有一天,小皮为了玩游戏,从结婚基金里面提取了一万块钱进行充值,但是小芳不知道,她一直以为结婚基金里面有10万块钱……
2、啥都别说先贴简单的示例代码
Money
类:
public class Money {
public static int money = 100000;
}
Test
类:
(为了方便我就使用lambda表达式+匿名内部类创建线程了)
public class Test {
public static void main(String[] args) {
new Thread(()->{
System.out.println("XiaoPiThread:Money.money:"+ Money.money);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Money.money = 90000;
System.out.println("XiaoPiThread:Money.money:"+Money.money);
}).start();
new Thread(()->{
System.out.println("XiaoFangThread:Money.money:"+Money.money);
while(Money.money == 100000){
///System.out.println("XiaoFangThread:Money.money:"+Money.money);
}
System.out.println("结婚基金已经不是十万了!");
}).start();
}
}
首先我们来预想一下执行结果,正常情况下是不是应该在小皮拿走10000以后,小芳这边一直在循环,总会知道Money.money
最后的值会变成90000,然后输出"结婚基金已经不是十万了!"
语句。这是我们预想的正常的结果,那么我们来看一下实际的运行结果:
XiaoPiThread:Money.money:100000
XiaoFangThread:Money.money:100000
XiaoPiThread:Money.money:90000
事实上,根据执行结果来看,只有小皮自己知道结婚基金现在最新的值是90000,但小芳却一直以为是100000,所以一直在死循环,导致没有输出我们预想的结果。但是为什么会这样呢?大家都是拿的Money.money
这个变量,按理说小皮线程在修改了这个变量以后,小芳线程总会拿到cpu执行权然后循环知道Money.money
已经是90000了,为什么没有输出我们想要的结果呢?其实,这个和线程的栈内存有关。
在JMM的定义里面,堆内存是唯一的,但是栈内存不一定。每一个线程都有自己的线程栈,当线程每次使用堆内存里面的变量时,会优先拷贝一份到变量的副本中,然后通过这个变量副本进行操作。
所以当小皮线程拿到cpu执行权,先睡眠,这时候小芳就会抢到cpu然后拿到堆内存中的Money.money==100000
,接着开始循环,在小芳循环的过程中,cpu可能随时被小皮线程抢走,然后执行Money.money = 90000
,把小皮线程中的变量副本Money.money
更改成90000,当小皮线程执行完毕后,线程会把变量副本的值赋值回堆内存中的变量,此时堆内存中的Money.money
已经是100000了,但是小芳线程却一直用的自己线程栈里面的变量副本,Money.money
一直是100000,所以她一直在死循环,线程也一直没有结束。
这也是这段代码存在的问题:当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题。
那我们怎么去解决这个问题呢?我们可以通过Volatile
关键字来解决。
Volatile
关键字 : 强制线程每次在使用的时候,都会看一下共享区域最新的值;
这段代码里的结婚基金是共享数据,我们希望这个数据在每次读写的时候,都是最新的,所以我们要改一下Money
类:
public class Money {
public static volatile int money = 100000;
}
我们在共享变量前面加上Volatile
关键字,这样线程每次在使用这个变量时,都会去看一下堆内存里最新的值。
输出结果:
XiaoPiThread:Money.money:100000
XiaoFangThread:Money.money:100000
XiaoPiThread:Money.money:90000
结婚基金已经不是十万了!
从结果可以看出,小芳线程已经知道了结婚基金的最新值不是100000了,最终输出结果结束进程。这样就符合我们的预想结果了。