关于线程访问另外一个线程的变量问题
之前帮别人调错,遇到一个错误,就是一个线程A访问另外一个线程B的成员变量,并且线程A在构造方法中有线程B的引用,但是无论线程A怎么获取线程B的成员变量都是 null,但是在线程B初始化中已经给成员变量赋值了。
后面解决了很久,没解决出来,然后刚好看到了 指令重排序问题 ,下面我们来看一个例子
public class Test {
int a = 0;
boolean flag = false;
public void writer(){
a = 1; //1
flag =true; //2
}
public void reader(){
if(flag){ //3
int i =a*a; //4
}
}
}
问:线程B在执行线程4时,能否看到线程A在操作1对共享变量a的写入呢?
不能
原因如下:
编译器和处理器 在不改变程序执行结构的前提下 会对代码进行重排序来优化,以提高程序的运行速度。
下面分析代码原因: 由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同意,操作3和操作4没有数据依赖关系,编译器和处理器也可以对这两个操作进行重排序。程序执行时序图如下:
如图所示, 操作1和操作2做了重排序。程序执行时,线程A首先写标记变量flag,随后线程B读取这个变量。由于条件判断为真,线程B将读取变量a 。此时,变量a没有被线程A写入,在这里多线程程序的语义被重排序破坏了!
那如何避免这种情况
加一个 volatile 关键字
因为volatile 关键字
保证了 该变量的可见性 (可见性是啥?要讲的话要花很长篇幅去将,感兴趣的读者去百度找找相关资料) , 当一个线程B写入成员变量的时候 ,线程B中的本地内存的成员变量的值立刻刷新到主内存中,当线程A要读取线程B的成员变量时,将本地内存的线程B的成员变量内存副本设置为无效,去主线程里面读取(这里也涉及到了 JMM内存模型)。
保证了 防止指令重排序问题,在关于这个变量的操作时候,不能让编译器和处理器进行指令重排序 这样就可以避免这个问题。
代码如下:
public class Test {
int a = 0;
volatile boolean flag = false;
public void writer(){
a = 1; //1
flag =true; //2
}
public void reader(){
if(flag){ //3
int i =a*a; //4
}
}
}
那么程序的执行时序图如下
这样就可以让程序正常运行。
PS:
因为这里涉及到线程的相关知识(JMM内存模型,指令重排序,happen-before原则,as-if-serial语义等知识感兴趣的读者可以买一本《Java并发编程的艺术》去看看),所以这篇文章讲的不是很透彻,只是给大家一个解决方案。本文章有若有诸多毛病,请大家包涵