内存可见性
- 一:内存可见性
- 1.2:
- 二:解决内存可见性问题
- 2.1 volatile关键字
- 2.2:synchronized关键字解决内存可见性问题
一:内存可见性
public class Demo1 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{while(count==0){}System.out.println("t1线程结束");});Thread t2=new Thread(()->{Scanner scanner=new Scanner(System.in);1System.out.println("请输入一个整数:");count=scanner.nextInt();});t1.start();t2.start();}
}
上述代码预期效果:
t1线程首先进入循环,当用户输入一个非0整数的时候,就会使t1线程退出循环.结束线程.
但t1实际上并没有真正出现退出的情况,这也是"bug",而产生上述现象的原因,就是**“内存可见性”**
while(count==0){}
从指令角度分析这段代码:
(1)load :从内存读取数据到CPU 寄存器中,
(2)cmp:比较,条件成立,继续循环,条件不成立,退出循环.
然而,一个load指令消耗的时间,会比一个cmp指令消耗的时间多得多,执行一次load的时间,等于上万次cmp执行消耗的时间.
同时,JVM发现每次load执行的结果,是一样的(t2线程修改之前),因此,**JVM就把上述load操作优化掉了,只是第一次真正进行load,后续再执行到load,而是直接读取已经load过的寄存器中的值了(读取寄存器的速度远远大于 读取内存的速度).**当t2线程修改count的值,但由于t1线程并没有从内存中重新读取,所以获取不到更新后的值.
1.2:
public class Demo1 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{while(count==0){System.out.println("hello ");}System.out.println("t1线程结束");});Thread t2=new Thread(()->{Scanner scanner=new Scanner(System.in);System.out.println("请输入一个整数:");count=scanner.nextInt();});t1.start();t2.start();}
}
当我们在
while(count==0){System.out.println("hello ");}
while循环中打印,就会发现代码又和我们预期的效果一样了,这又是为什么???
因为循环体内存在IO操作,而IO操作是从硬盘中获取数据,因此IO操作消耗的时间比load操作消耗的时间更多,并且IO操作是不能被优化掉的.
总结:上述问题本质上是编译器优化引起的(优化是由javac和java配合完成的工作),优化掉load操作之后,使t2线程的修改,没有被t1线程感知到,这就造成了"内存可见性"问题
二:解决内存可见性问题
2.1 volatile关键字
编译器什么时候优化,什么时候不优化,这是一个"玄学问题".
通过volatile关键字,解决优化问题,让编译器不再优化.
当给变量修饰上volatile关键字之后,编译器就知道了,这个变量是"反复无常"的,编译器就不会再进行优化了
volatile 是专门针对内存可见性的场景来解决问题的,告诉编译器不要进行优化操作.
public class Demo1 {public volatile static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{while(count==0){}System.out.println("t1线程结束");});Thread t2=new Thread(()->{Scanner scanner=new Scanner(System.in);System.out.println("请输入一个整数:");count=scanner.nextInt();});t1.start();t2.start();}
}
2.2:synchronized关键字解决内存可见性问题
synchronized关键字,和volatile关键字处理逻辑上是不同的.
引入synchronized关键字,是因为加锁操作本身太重量了,相比load 来说,开销更大,编译器自然就不会对load 优化了(和sleep ,IO操作原理类似).
public class Demo1 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1=new Thread(()->{while(count==0){synchronized (locker){{}}}System.out.println("t1线程结束");});Thread t2=new Thread(()->{Scanner scanner=new Scanner(System.in);System.out.println("请输入一个整数:");count=scanner.nextInt();});t1.start();t2.start();}
}