static能保证可见性吗?当然不能!!!
我们知道在JAVA中想要保证可见性,要么使用volatile,要么使用synchronized,从来没有听说使用static来保证可见性的。
那么既然大家都知道static无法保证可见性,为什么还是有很多人和我一样会发出这样的疑问呢?我想,大概率是在学习多线程的时候发现,某些时候static似乎是保证了可见性,但是大家又都不承认它能保证可见性。那么问题就来了,static真的不能保证可见性吗?当然,事实也确实如此。
空说无凭,实践是检验真理的唯一标准。
我们先来看一段小程序
private static String msg = null;
// static 能保证可见性?
static void enable() {
new Thread(() -> {
while(true) {
msg = "T1";
if(!"T1".equals(msg)) {
System.out.println("T1 break");
}
}
}).start();
new Thread(() -> {
while(true) {
msg = "T2";
if(!"T2".equals(msg)) {
System.out.println("T2 break");
}
}
}).start();
}
public static void main(String[] args) {
enable();
}
程序比较简单,开启两个线程做死循环,第一个线程将msg改为 T1,然后判断msg是否是T1;另一个线程将msg改为T2,然后msg是否是 T2。如果不是则打印相应的输出,我们来看看程序运行的结果,由于是死循环所以只截取了部分。
T2 break
T2 break
T2 break
T2 break
T2 break
T1 break
T1 break
T1 break
T2 break
T1 break
T1 break
T2 break
T2 break
T2 break
....
可以看到不管是T1还是T2都打印了,说明即使在刚刚改完 msg 的值,也可能得到不一样的值。那么是不是就可以说明,static修饰的变量,在一个线程中发生了改变其他线程就能感知呢?换句话说,static是不是保证了可见性呢?
当然,从这个实验中你似乎可以这么理解了,但是事实真是如此吗?我们再来看下一段程序。
private static int MY_INT = 0;
// static 不能保证可见性
static void disable() {
new ChangeListener().start();
new ChangeMaker().start();
}
static class ChangeListener extends Thread {
@Override
public void run() {
int local_value = MY_INT;
while (local_value < 5){
if(local_value != MY_INT){
System.out.println("Got Change for MY_INT: " + MY_INT);
local_value = MY_INT;
}
}
}
}
static class ChangeMaker extends Thread {
@Override
public void run() {
int local_value = MY_INT;
while (MY_INT < 5){
System.out.println("Increment MY_INT to " + (local_value+1));
MY_INT = ++local_value;
try {
Thread.sleep(500);
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
public static void main(String[] args) {
disable();
}
这个实验定义了两个线程,一个用于将MY_INT从0加到5,一个用于读MY_INT,和线程内local_value不一致则更新local_value。来看看运行结果。
Increment MY_INT to 1
Increment MY_INT to 2
Increment MY_INT to 3
Increment MY_INT to 4
Increment MY_INT to 5
可以看到ChangeMaker线程运行完了,但是ChangeListener线程却一直处于循环中,说明MY_INT的改变并没有被ChangeListener线程感知,即MY_INT的改变对ChangeListener线程是不可见的。
如果想要让ChangeListener线程可见,我们可以试试给MY_INT加上volatile关键字。
private static volatile int MY_INT = 0;
// static 不能保证可见性, volatile 可以
static void disable() {
new ChangeListener().start();
new ChangeMaker().start();
}
static class ChangeListener extends Thread {
@Override
public void run() {
int local_value = MY_INT;
while (local_value < 5){
if(local_value != MY_INT){
System.out.println("Got Change for MY_INT: " + MY_INT);
local_value = MY_INT;
}
}
}
}
static class ChangeMaker extends Thread {
@Override
public void run() {
int local_value = MY_INT;
while (MY_INT < 5){
System.out.println("Increment MY_INT to " + (local_value+1));
MY_INT = ++local_value;
try {
Thread.sleep(500);
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
public static void main(String[] args) {
disable();
}
再来看看运行结果
Increment MY_INT to 1
Got Change for MY_INT: 1
Increment MY_INT to 2
Got Change for MY_INT: 2
Increment MY_INT to 3
Got Change for MY_INT: 3
Increment MY_INT to 4
Got Change for MY_INT: 4
Increment MY_INT to 5
Got Change for MY_INT: 5
交替打印,程序正常退出。说明 volatile 保证了可见性,static无法保证。
以上,通过实验说明了static无法保证可见性,想要保证可见性还是得靠volatile。
以下是我个人对static和volatile的理解,如有错误请指出。
static 只是说明这个变量为该类的所有实例所共有,但是线程访问该变量时还是从主内存拷贝一份回工作内存,至于什么时候同步回主内存那就不好说了。
volatile 表示即使你从主内存拷贝到了工作内存,你每次要用的时候还是要去主内存同步,如果变量发生了改变就拿最新的,如果当前线程改变了这个变量也要写回到主内存。