您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# CAS非锁怎样实现单例的一个缺陷
## 引言
在并发编程中,单例模式是最常用的设计模式之一。为了保证线程安全,传统的实现方式会使用`synchronized`关键字或`Lock`进行加锁。而基于CAS(Compare-And-Swap)的无锁实现因其高性能特性逐渐流行,但这种实现方式存在一个容易被忽视的缺陷——**指令重排序导致的初始化问题**。本文将深入分析这一缺陷的原理、表现及解决方案。
---
## 一、CAS无锁单例的典型实现
以下是基于CAS的经典无锁单例实现代码:
```java
public class CASSingleton {
private static final AtomicReference<CASSingleton> INSTANCE = new AtomicReference<>();
private CASSingleton() {}
public static CASSingleton getInstance() {
for (;;) {
CASSingleton instance = INSTANCE.get();
if (instance != null) {
return instance;
}
instance = new CASSingleton();
if (INSTANCE.compareAndSet(null, instance)) {
return instance;
}
}
}
}
AtomicReference
保证原子性操作for(;;)
)直到成功创建实例上述代码看似完美,但实际上存在一个可见性问题:由于JVM的指令重排序优化,对象的初始化可能未完成就被其他线程访问。
当执行new CASSingleton()
时,JVM会隐式执行三个步骤:
1. 分配对象内存空间
2. 初始化对象(调用构造函数)
3. 将引用指向内存地址(写入主存)
JVM可能将步骤2和步骤3重排序,导致其他线程获取到未初始化完成的对象。时序如下:
线程A | 线程B |
---|---|
1. 分配内存 | |
3. 引用指向内存(未初始化) | |
2. 检测到INSTANCE非null并返回 | |
2. 执行构造函数初始化 |
通过以下代码可以复现问题:
public class CASSingleton {
private int value = 42; // 需要初始化的字段
public static void main(String[] args) {
new Thread(() -> {
CASSingleton instance = getInstance();
System.out.println(instance.value); // 可能输出0
}).start();
}
}
可能输出结果:0
(而非预期的42
),证明对象未完成初始化就被访问。
private static volatile CASSingleton instance;
public static CASSingleton getInstance() {
CASSingleton temp = instance;
if (temp == null) {
synchronized (CASSingleton.class) {
temp = instance;
if (temp == null) {
temp = new CASSingleton();
instance = temp;
}
}
}
return temp;
}
public class HolderSingleton {
private static class Holder {
static final CASSingleton INSTANCE = new CASSingleton();
}
public static CASSingleton getInstance() {
return Holder.INSTANCE;
}
}
private static final AtomicReference<CASSingleton> INSTANCE = new AtomicReference<>();
public static CASSingleton getInstance() {
CASSingleton instance = INSTANCE.get();
if (instance == null) {
instance = new CASSingleton();
if (INSTANCE.compareAndSet(null, instance)) {
return instance;
}
}
return INSTANCE.get(); // 始终通过AtomicReference获取最新值
}
方案 | 性能 | 代码复杂度 | 安全性 |
---|---|---|---|
原始CAS实现 | ★★★★☆ | ★★☆☆☆ | ★☆☆☆☆ |
volatile+DCL | ★★★☆☆ | ★★★☆☆ | ★★★★★ |
静态内部类 | ★★★★★ | ★☆☆☆☆ | ★★★★★ |
AtomicReference优化 | ★★★★☆ | ★★★☆☆ | ★★★★☆ |
volatile+双重检查锁
或静态内部类实现“并发编程的艺术在于,不仅要考虑代码的正确性,还要理解运行时环境的微妙特性。” —— Brian Goetz
”`
(全文约1100字,可根据具体排版调整)
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。