首先来看一段代码
public class Solution {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true) {
//判断td中flag的值
if(td.isFlag())
System.out.println("main:flag is true");
}
}
}
class ThreadDemo implements Runnable {
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
flag = true;
System.out.println("Thread1:now flag is true");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
运行结果如下:
从结果可以看出,在线程td中已经令flag的值为true,但在main线程中获得的flag值却为false;
原因分析:
在程序运行时,JVM为了提高运行效率,会给每条线程分配一小块空间作为缓存(上图中的cache);程序开始运行时,在主内存(Main Memory)中初始化flag=false;当td修改flag为true时,先把flag的值从主内存复制到cache中,再把cache中修改flag=true,但并没有立刻同步到主内存中,所以Main线程获取到的flag为false;又由于Main线程中while(true)执行的速度很快又一直在循环,之后就算主内存中flag的值已经修改为true,也没有机会把flag的值从主内存中同步过来。
这里就引出了内存可见性问题,多条线程之间对于共享数据的操作,彼此是不可见的。
此问题可以通过加锁解决,加入synchronized中可以保证每次获取flag时都是在主内存中最新的值,但加锁必然会导致程序的运行效率下降,代码如下:
public class Solution {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true) {
//加锁
synchronized (td) {
// 判断td中flag的值
if (td.isFlag()) {
System.out.println("main:flag is true");
break;
}
}
}
}
}
class ThreadDemo implements Runnable {
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
flag = true;
System.out.println("Thread1:now flag is true");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
最好还是用volatile修饰flag:
public class Solution {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true) {
synchronized (td) {
// 判断td中flag的值
if (td.isFlag()) {
System.out.println("main:flag is true");
break;
}
}
}
}
}
class ThreadDemo implements Runnable {
//flag加上volatile修饰
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
flag = true;
System.out.println("Thread1:now flag is true");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的
2)禁止进行指令重排序
3)volatile不保证原子性(具体可参考JUC简笔2-多线程下的非原子操作)