不能。volatile仅抑制编译器优化,不提供原子性或happens-before关系;std::atomic需配合memory_order_acquire/release才能确保构造完成与指针发布的顺序一致性。

为什么直接用 volatile 不能解决双重检查锁定问题
C++11 之前,volatile 常被误用来防止指令重排,但它在 C++ 中只保证内存可见性,不约束编译器和 CPU 的重排序行为。比如 new Singleton() 实际分三步:分配内存 → 调用构造函数 → 将地址写入静态指针。若第二、三步被重排,另一个线程可能拿到未初始化完成的对象指针,触发未定义行为。
-
volatile在 C++ 中不是内存屏障,对多线程同步无效 - 必须使用
std::atomic+ 显式内存序控制 - C++11 标准库提供了可靠的原子操作支持,不用手写汇编或依赖编译器扩展
如何用 std::atomic 实现线程安全的双重检查
核心是把单例指针声明为 std::atomic<singleton></singleton>,并配合 memory_order_acquire 和 memory_order_release 控制读写顺序:
class Singleton {
public:
static Singleton* getInstance() {
Singleton* instance = instance_.load(std::memory_order_acquire);
if (instance == nullptr) {
std::lock_guard<:mutex> lock(mutex_);
instance = instance_.load(std::memory_order_relaxed);
if (instance == nullptr) {
instance = new Singleton();
instance_.store(instance, std::memory_order_release);
}
}
return instance;
}
<p>private:
Singleton() = default;
static std::atomic<singleton>> instance<em>;
static std::mutex mutex</em>;
};
std::atomic<singleton>> Singleton::instance_{nullptr};</singleton></singleton></p></:mutex>
- 第一次
load用memory_order_acquire,确保后续读取看到构造完成后的状态 - 第二次
load可用memory_order_relaxed,因为已持锁 -
store必须用memory_order_release,与 acquire 配对形成同步关系 - 构造函数必须是无异常的,否则
new失败时指针已写入但对象未就绪
为什么 C++11 后推荐用局部静态变量替代手动双重检查
C++11 标准规定:函数内局部静态变量的初始化是线程安全的,且只执行一次。编译器会自动插入必要的同步逻辑(如调用 __cxa_guard_acquire 等):
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // 线程安全,无需锁、无需原子
return instance;
}
private:
Singleton() = default;
};
- 更简洁,无手动同步逻辑,不易出错
- 编译器生成的代码通常比手写双重检查更高效(例如避免原子操作开销)
- 析构时机确定(程序退出时按构造逆序销毁),但要注意:若需控制析构顺序或延迟销毁,此方案不适用
- 不适用于需要参数化构造或工厂式创建的场景
双重检查锁定在现代 C++ 中基本是历史遗留写法;真正需要精细控制生命周期或跨平台兼容旧标准时,才值得手动实现——而那类场景里,最容易被忽略的是构造函数异常安全性和内存序配对是否严格一致。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











