Java 中的双重检查锁定模式:在最小开销下确保线程安全
大约 2 分钟
双重检查锁定设计模式的意图
通过首先在实际获取锁之前测试锁定条件(“锁提示”)来减少获取锁的开销。只有在锁定条件似乎为真时,才会继续执行实际的锁定逻辑。Java 中的双重检查锁定有助于优化性能并确保线程安全。
双重检查锁定模式的详细解释以及现实世界中的示例
现实世界中的示例
在一个拥有高价值设备室的公司里,员工首先检查一个可见的标志,查看房间是否被锁。如果标志显示未锁,他们直接进入;如果被锁,他们使用安全钥匙卡进行访问。这种两步验证过程有效地管理了安全性,而无需不必要地使用电子锁系统,这反映了软件中使用的双重检查锁定模式,以最大限度地减少资源密集型操作。
通俗地说
软件中的双重检查锁定模式通过在进行更资源密集的锁定操作之前,首先以低成本的方式检查锁状态,最大限度地减少了昂贵的锁定操作,从而在对象初始化期间确保效率和线程安全。
维基百科说
在软件工程中,双重检查锁定(也称为“双重检查锁定优化”)是一种软件设计模式,用于通过在获取锁之前测试锁定条件(“锁提示”)来减少获取锁的开销。只有在锁定条件检查表明需要锁定时,才会进行锁定。
Java 中双重检查锁定模式的编程示例
双重检查锁定模式用于 HolderThreadSafe
类,以确保即使从多个线程访问,Heavy
对象也只创建一次。以下是它的工作原理
- 检查对象是否已初始化(第一次检查):如果是,立即返回它。
if (heavy == null) {
// ...
}
- 同步创建对象的代码块:这确保只有一个线程可以创建对象。
synchronized (this) {
// ...
}
- 再次检查对象是否已初始化。如果在当前线程进入同步块时,另一个线程已经创建了对象,则返回已创建的对象。
if (heavy == null) {
heavy = new Heavy();
}
- 返回已创建的对象。
return heavy;
以下是 HolderThreadSafe
类的完整代码
public class HolderThreadSafe {
private Heavy heavy;
public HolderThreadSafe() {
LOGGER.info("Holder created");
}
public synchronized Heavy getHeavy() {
if (heavy == null) {
synchronized (this) {
if (heavy == null) {
heavy = new Heavy();
}
}
}
return heavy;
}
}
在此代码中,Heavy
对象仅在第一次调用 getHeavy
方法时创建。这称为延迟初始化。双重检查锁定模式用于确保即使从多个线程同时调用 getHeavy
方法,Heavy
对象也只创建一次。
何时在 Java 中使用双重检查锁定模式
当满足以下所有条件时,在 Java 中使用双重检查锁定模式
- 存在一个创建成本很高的单例资源。
- 需要减少每次访问资源时获取锁的开销。
Java 中双重检查锁定模式的实际应用
- 多线程环境中的单例模式实现。
- Java 应用程序中资源密集型对象的延迟初始化。
双重检查锁定模式的优缺点
优点
- 在对象初始化后避免不必要的锁定带来的性能提升。
- 为关键的初始化部分维护线程安全。
权衡
- 复杂的实现可能会导致错误,例如由于内存可见性问题而导致对象发布不正确。
- 在 Java 中,除非小心使用 volatile 变量,否则它在某些版本中可能是冗余的或失效的。