读书要趁黑发早,白首不悔少当时
文章目录
- 1. 什么是内存可见性问题
- 2. 避免内存可见性问题-----volatile(易变的)
- 3. 需要注意的点
- 总结
1. 什么是内存可见性问题
在线程A在读一个变量的时候,另一个线程B在修改这个变量,所以,线程A读到的值不是修改之后的,是一个未更新的值,读到的值是错误的.
如下代码,t1线程进行一个循环,循环条件是c.count = 0,线程2进行修改c.count值的操作.正常来说,t2线程修改了count的值,t1线程循环条件不满足,会跳出循环,打印,之后结束进程.但现实结果是,t1的循环一直没有结束,大家思考,这是为什么呢?
class Counter{
public int count = 0;
}
public class Volatile {
public static void main(String[] args) {
Counter c = new Counter();
Thread t1 = new Thread(()->{
while(c.count == 0){
}
System.out.println("t1线程要结束啦~");
});
Thread t2 = new Thread(()->{
Scanner in = new Scanner(System.in);
int i = in.nextInt();
c.count = i;
System.out.println("t2线程要结束啦~");
});
t1.start();
t2.start();
}
}
执行结果如下,修改了count值后,线程t1一直没有结束
t1的循环条件,c.count = 0,这个比较操作需要两个具体操作才能完成.
1.每次将count的值读取到寄存器上,即load.
2.将寄存器中count的值与0进行比较,即cmp.
由于t1的循环执行速度非常快,1s能执行上百万次,并且比较值的操作cmp比读取值到寄存器的操作load要快得多,所以,这里编译器发现这里t1的循环读取的值貌似一直都是一个数,所以,这里编译器自作主张对程序做了个优化,只读一次count值,之后的循环都按第一次读到的值来进行比较.
正常时候,这个优化是没问题的,但这个是多线程程序,t2线程对count值进行了修改,t1没有察觉到,还是按第一次读取到的值0来进行比较,出现了线程安全问题----内存可见性问题,一个线程读,一个线程改,读到的数是修改之前的值,是错误的值.
2. 避免内存可见性问题-----volatile(易变的)
如下代码,用volatile修饰变量,这个操作是在告诉编译器,这个变量值有其他线程能修改,是能变化的值,防止编译器自作主张进行优化,避免只读取一次值的行为.t1线程每次循环都要重新读一次count值.
class Counter{
volatile public int count = 0;
}
修改后,程序结果如下.
3. 需要注意的点
volatile不能修饰方法里的局部变量.由于不同线程调用方法时,都会开辟自己的栈空间,去单独使用变量,不同进程之间互不影响.(C++中volatile可以修饰局部变量,因为C++可以将线程A的局部变量给线程B使用)
总结
内存可见性问题出现在多线程中一线程读,一线程写造成的问题,由volatile修饰,防止编译器进行优化,每次重新读取值.