合作机构:阿里云 / 腾讯云 / 亚马逊云 / DreamHost / NameSilo / INWX / GODADDY / 百度统计
在 Java 并发编程中,有 3 个最常用的关键字:synchronized、ReentrantLock 和 volatile。
虽然 volatile 并不像其他两个关键字一样,能保证线程安全,但 volatile 也是并发编程中最常见的关键字之一。例如,单例模式、CopyOnWriteArrayList 和 ConcurrentHashMap 中都离不开 volatile。
那么,问题来了,我们知道 synchronized 底层是通过监视器 Monitor 实现的,ReentrantLock 底层是通过 AQS 的 CAS 实现的,那 volatile 的底层是如何实现的?
在了解 volatile 的底层实现之前,我们需要先了解 volatile 的作用,因为 volatile 的底层实现和它的作用息息相关。
volatile 作用有两个:保证内存可见性和有序性(禁止指令重排序)。
说到内存可见性问题就不得不提 Java 内存模型,Java 内存模型(Java Memory Model)简称为 JMM,主要是用来屏蔽不同硬件和操作系统的内存访问差异的,因为在不同的硬件和不同的操作系统下,内存的访问是有一定的差异得,这种差异会导致相同的代码在不同的硬件和不同的操作系统下有着不一样的行为,而 Java 内存模型就是解决这个差异,统一相同代码在不同硬件和不同操作系统下的差异的。
Java 内存模型规定:所有的变量(实例变量和静态变量)都必须存储在主内存中,每个线程也会有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量,如下图所示:
然而,Java 内存模型会带来一个新的问题,那就是内存可见性问题,也就是当某个线程修改了主内存中共享变量的值之后,其他线程不能感知到此值被修改了,它会一直使用自己工作内存中的“旧值”,这样程序的执行结果就不符合我们的预期了,这就是内存可见性问题,我们用以下代码来演示一下这个问题:
private static boolean flag = false;
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (!flag) {
}
System.out.println("终止执行");
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("设置 flag=true");
flag = true;
}
});
t2.start();
}
TOP