可见性介绍
在Java语言中,多线程并发执行是一种常见的编程方式。这种方式可以提高程序的执行效率,但同时也带来了一些问题,如共享变量的可见性问题。当多个线程同时访问一个共享变量时,由于线程之间的执行顺序是不确定的,可能会导致某个线程修改了变量的值,但其他线程却无法感知到这个变化。这就是可见性问题。
可见性问题是由于多个线程之间的执行顺序不确定性导致的。在多线程编程中,为了提高执行效率,线程之间的指令重排序是允许的。指令重排序可以改变指令的执行顺序,但不会改变程序的执行结果。然而,指令重排序可能会导致共享变量的可见性问题。当一个线程修改了一个共享变量的值后,它可能会将修改后的值缓存在寄存器或者CPU的缓存中,并没有立即写回主内存。其他线程在读取这个共享变量时,可能会读取到缓存中的旧值,而不是最新的值。
为了解决可见性问题,Java提供了一些机制,如使用volatile关键字修饰共享变量、使用synchronized关键字进行同步等。本文将主要介绍volatile关键字的使用和原理。
volatile关键字
在Java中,volatile关键字可以用来修饰共享变量,它的作用是告诉虚拟机,这个变量是可能被多个线程同时访问的,不要对它进行指令重排序的优化。当一个线程修改了一个volatile变量的值后,它会立即写回主内存,并且会通知其他线程,使它们从主内存中重新获取最新的值。
下面是一个示例代码,演示了volatile关键字的使用:
public class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true;
}
public void reader() {
while (!flag) {
// do something
}
}
}
在上面的示例中,flag变量被volatile关键字修饰。writer方法会将flag变量设置为true,而reader方法会循环检查flag变量的值,直到它变为true才会退出循环。
使用volatile关键字修饰的变量,保证了其在多个线程之间的可见性。即当一个线程修改了这个变量的值后,其他线程能够立即感知到这个变化。在上面的示例中,如果没有使用volatile关键字修饰flag变量,reader方法的循环可能会陷入无限循环,因为它无法感知到writer方法对flag变量的修改。
需要注意的是,volatile关键字只能保证可见性,不能保证原子性。即,当一个线程正在修改一个volatile变量的值时,其他线程仍然可以读取这个变量的旧值。如果需要保证原子性,可以使用synchronized关键字或者Lock来进行同步。
可见性问题示例与解决方案
下面通过一个示例来演示可见性问题以及使用volatile关键字解决可见性问题的方法。
public class VisibilityExample {
private volatile boolean flag = false;
public void writer() {
flag = true;
}
public void reader() {
while (!flag) {
// do something
}
System.out.println("flag is true");
}
public static void main(String[] args) {
VisibilityExample example = new VisibilityExample();
new Thread(() -> {
example.writer();
}).start();
new Thread(() -> {
example.reader();
}).start();
}
}
在上面的示例中,有两个线程分别执行writer和reader方法。writer方法将flag变量设置为true,而reader方法会循环检查flag变量的值,并在flag变为true时输出一条信息。
如果不使用volatile关键字修饰flag变量,reader方法可能会陷入无限循环,因为它