java多线程的内存可见性问题,volatile是干什么的?

#新星杯·14天创作挑战营·第10期#

引入

首先我们用一段代码引出我们的问题,什么是内存可见性问题?

static class Counter {
 public int flag = 0;
}
public static void main(String[] args) {
	 Counter counter = new Counter();
	 Thread t1 = new Thread(() -> {
		 while (counter.flag == 0) {
		 
		 }
		 System.out.println("循环结束!");
	 });
	 Thread t2 = new Thread(() -> {
		 Scanner scanner = new Scanner(System.in);
		 System.out.println("输⼊⼀个整数:");
		 counter.flag = scanner.nextInt();
	 });
	 t1.start();
	 t2.start();
}

在这段代码中我们想让我们输入的值不是0的时候t1线程结束,那我们试试结果呢?

这里是引用

很明显当我们输入的时候循环并没有暂停,这时就说明我们的程序出现Bug了,而这就是我们要讲的内存可见性问题。

产生原因

内存可见性问题是由编译器优化导致的,一个线程读取,一个线程修改,修改的值并没有被读取线程读到。

编译器,虽然声称优化操作,是能够保证逻辑不变,尤其是在多线程的程序中,编译器的判断可能出现失误可能导致编译器的优化,使优化后的逻辑,和优化前的逻辑出现细节上的偏差。

jvm不知道什么时候才会进行修改,而且它的值始终没变所以将读取内存优化成了读取寄存器,因为寄存器比内存快,而这就导致了它无法识别到内存的修改,导致了误判。所以说t1的读操作不是真正的读内存。

解决方案

sleep

通过sleep()可以让循环时间延长,让其不再优化load()操作(因为相对于整个循环优化的时间无足轻重)。

但是sleep()方法代价太大了,因此引用了volatile(易变的)关键字,既然你编译器无法判别要不要优化,我就手动判别,当它修饰某个关键字的时候,不会把它优化成读寄存器。

volatile

volatile 关键字的作用主要有如下两个:

  1. 保证内存可见性 :基于屏障指令实现,即当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
  2. 保证有序性:禁止指令重排序。编译时 JVM 编译器遵循内存屏障的约束,运行时靠屏障指令组织指令顺序。

指令重排序:也是编译器优化的一种形式,调整代码运行的先后顺序,以得到提高性能的效果。指令重排序的大前提是逻辑不变,在多线程的环境下,这里的判定可能出现失误。

votaile是用来修饰变量的,当它修饰的时候jvm就不会对这个变量进行读寄存器的优化,而我们之前的问题就可以迎刃而解了~

 public volatile int flag = 0;

感谢各位的观看~如果对你有帮助的话留个关注再走吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值