题目
实现线程安全的单例模式
信息
- 类型:问答
- 难度:⭐⭐
考点
单例模式,线程安全,类变量与类方法,懒加载
快速回答
实现线程安全的单例模式需要:
- 使用类变量存储唯一实例
- 通过类方法提供全局访问点
- 使用
Mutex确保线程安全 - 实现懒加载(首次调用时初始化)
- 将
new设为私有方法防止外部实例化
原理说明
单例模式确保一个类只有一个实例,并提供全局访问点。线程安全要求在多线程环境下:
- 防止多次实例化
- 避免返回未完全初始化的对象
- 保证状态一致性
Ruby 中需使用 Mutex 同步临界区(实例化代码),结合双重检查锁定优化性能。
代码示例
require 'thread'
class Configuration
@@instance = nil
@@mutex = Mutex.new
# 类方法提供全局访问点
def self.instance
return @@instance if @@instance
@@mutex.synchronize do
# 双重检查锁定:进入同步块后再次检查
@@instance ||= new
end
@@instance
end
# 私有化构造函数
private_class_method :new
# 实例方法示例
def settings
@settings ||= load_settings
end
private
def load_settings
# 模拟耗时操作
sleep(0.1)
{ env: 'production', timeout: 30 }
end
end
# 测试线程安全
threads = []
5.times do
threads << Thread.new { Configuration.instance }
end
threads.each(&:join)
puts "实例ID: #{Configuration.instance.object_id}" # 所有线程输出相同ID最佳实践
- 双重检查锁定:同步块外先检查实例,减少锁竞争
- 懒加载:首次调用时初始化,避免启动开销
- 私有构造函数:防止
new直接创建实例 - 模块化:复杂场景可使用
Singleton模块(标准库)
常见错误
- 缺少双重检查:
@@mutex.synchronize { @@instance ||= new }未在同步块外检查实例,导致每次访问都加锁 - 类变量未初始化:忘记设置
@@instance = nil - 非原子操作:未用互斥锁包裹实例化过程
- 过早优化:在明确无并发场景下过度使用同步
扩展知识
- GIL 的影响:MRI Ruby 有全局解释器锁,但 I/O 操作期间会释放锁,仍需同步控制
- Singleton 模块:标准库简化实现(
require 'singleton'+include Singleton) - 依赖注入替代:单例可能增加耦合度,可考虑依赖注入容器
- 序列化问题:单例序列化/反序列化时需重写
_dump和_load方法