关于volatile关键字要清楚的地方
引言
在Java多线程编程中,
volatile
关键字扮演着至关重要的角色。它简单而强大,能够确保共享变量的可见性和有序性。本文将深入探讨volatile
关键字的原理、使用场景以及它在Java内存模型中的作用。
什么是volatile关键字?
volatile
是Java中的一个基本关键字,用于修饰变量。当一个变量被声明为volatile
时,它保证每次访问变量时都会从主内存中读取,而不是从线程的工作内存中读取。这意味着,当一个线程修改了volatile
变量时,其他线程能够立即看到这个改变。
JMM(Java Memory Model)java内存模型
- 主内存(Main Memory) : 主内存是所有线程共享的内存区域,存储了Java程序中的共享变量。线程对共享变量的读写操作都必须通过主内存来完成。
- 工作内存(Thread Working Memory) : 每个线程都有自己的工作内存,用于存储线程私有的数据和共享数据的副本。线程对共享变量的所有操作都必须在工作内存中进行,不能直接操作主内存中的变量。
可见性
在多线程环境中,线程通常会将变量复制到自己的工作内存中进行操作,以提高效率。但是,这种优化可能会导致一个问题:一个线程对变量的修改可能不会立即反映到其他线程的工作内存中。这就是所谓的“可见性问题”。
通过将变量声明为volatile
,我们可以确保所有线程看到的变量值是一致的。这是因为volatile
变量的写操作会直接作用于主内存,并且每次读取都会从主内存中获取最新的值。
有序性
volatile
关键字还能防止指令重排序。在Java中,编译器和处理器可能会对指令进行重排序,以提高性能。然而,这种优化可能会导致多线程程序出现意外的行为。
当变量声明为volatile
时,它会在每次写操作后插入一个写内存屏障(Store Memory Barrier),并在每次读操作前插入一个读内存屏障(Load Memory Barrier)。这些内存屏障确保了volatile
变量的操作在内存中是有序的。
volatile使用场景
- 标志和状态:当
volatile
用于布尔标志或状态变量时,它可以确保线程之间的状态同步。 - 双重检查锁定:在实现单例模式时,
volatile
可以防止指令重排序,确保单例对象的正确创建。
volatile实现双重校验锁
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
-
私有构造函数:为了确保不能通过
new
关键字直接创建实例,我们将构造函数设置为私有。 -
静态变量
instance
:这是单例对象的引用,初始时设置为null
。 -
volatile
关键字:在instance
变量前使用volatile
关键字,确保了变量的读写操作对所有线程立即可见。这是双重检查锁定模式能够正常工作的关键。 -
getInstance()
方法:- 首先,检查
instance
是否已经被初始化,如果已经初始化,直接返回instance
。 - 如果
instance
为null
,则进入同步块。这里的同步机制确保了只有一个线程能够进入并创建实例。 - 在同步块内部,再次检查
instance
是否为null
,这是因为在进入同步块的瞬间,其他线程可能已经创建了实例。这是双重检查的真正意义所在。 - 如果
instance
仍然为null
,则创建单例对象,并返回。
- 首先,检查
限制与替代
虽然volatile
关键字非常强大,但它并不能替代所有的同步操作。volatile
不能保证复合操作的原子性,例如自增操作(i++
)。在这种情况下,我们需要使用锁(synchronized
关键字)或原子类(如AtomicInteger
)来保证操作的原子性。
转载自:https://juejin.cn/post/7352891079295942691