目录
单例模式的定义
单例的定义:“一个类只允许创建唯一一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。”
百度百科:
线程唯一
如何实现一个线程唯一的单例模式呢?
首先线程唯一,那就首先想到了此时线程要作为一个映射的key,然后取这个值。
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static final ConcurrentHashMap<Long, IdGenerator> instances = new ConcurrentHashMap<>();
private IdGenerator() {
}
public static IdGenerator getInstance() {
Long currentThreadId = Thread.currentThread().getId();
instances.putIfAbsent(currentThreadId, new IdGenerator());
return instances.get(currentThreadId);
}
public long getId() {
return id.incrementAndGet();
}
}
还有一种方式就是用ThreadLocal了,原理其实差不多,只不过这个映射关系直接被放在Thread类里面了。
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static final ThreadLocal<IdGenerator> instances = new ThreadLocal<>();
private IdGenerator() {
}
public static IdGenerator getInstance() {
final IdGenerator idGenerator = instances.get();
if(idGenerator == null){
final IdGenerator value = new IdGenerator();
instances.set(value);
return value;
}
return idGenerator;
}
public long getId() {
return id.incrementAndGet();
}
}
进程唯一
懒汉式(线程不安全)
这也是最经典的一种实现。是有线程安全问题的。
public class LazySingleton {
private static LazySingleton instance;
public LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
饿汉式
用空间换时间,在线程还没出现之前就已经实例化了,是线程安全的。
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
//防止外部实例化
private EagerSingleton(){
}
public static EagerSingleton getInstance(){
return instance;
}
public static void main(String[] args) {
EagerSingleton eagerSingleton = EagerSingleton.getInstance();
}
}
懒汉式(线程安全)
public class LazySingleton {
private static LazySingleton instance;
public LazySingleton() {
}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
内部静态类
使用类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,同时不会因为加锁的方式耗费性能。
这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确的加载。
public class InnerSingleton {
private static class SingletonHolder {
private static InnerSingleton instance = new InnerSingleton();
}
private InnerSingleton() {
}
public static InnerSingleton getInstance() {
return SingletonHolder.instance;
}
}
双检锁
本质上还是懒汉式,是线程安全的,instance需要被volatile修饰否则会有线程安全问题。
这个问题在于 EagerSingleton instance=new EagerSingleton()
这行代码并不是一个原子指令。使用 javap -c
指令,可以快速查看字节码。
// 创建 EagerSingleton 对象实例,分配内存
0: new #5 // class com/cx/EagerSingleton
// 复制栈顶地址,并再将其压入栈顶
3: dup
// 调用构造器方法,初始化 Cache 对象
4: invokespecial #6 // Method "<init>":()V
// 存入局部方法变量表
7: astore_1
从字节码可以看到创建一个对象实例,可以分为三步:
- 分配对象内存
- 调用构造器方法,执行初始化
- 将对象引用赋值给变量。
虚拟机实际运行时,以上指令可能发生重排序。以上代码 2,3 可能发生重排序,但是并不会重排序 1 的顺序。也就是说 1 这个指令都需要先执行,因为 2,3 指令需要依托 1 指令执行结果。
Java 语言规规定了线程执行程序时需要遵守 intra-thread semantics。**intra-thread semantics ** 保证重排序不会改变单线程内的程序执行结果。这个重排序在没有改变单线程程序的执行结果的前提下,可以提高程序的执行性能。
虽然重排序并不影响单线程内的执行结果,但是在多线程的环境就带来一些问题。
volatile的底层使用CPU指令禁止重排序,保证了多线程的安全性。
public class LazySingleton {
private static volatile LazySingleton instance;
public LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
枚举单例
public enum EnumSingleton {
INSTANCE;
// 添加其他成员变量和方法
public void doSomething() {
// 单例实例的操作
}
}
集群唯一
- 我们需要把这个单路模式序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例模式的时候,需要先从外部共享存储器中并将它读取到内存,并反序列化成对象,然后再使用,使用完成后还需要再存储回外部存储区。
- 为了保证任何时刻,在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,还需要显式地将对象从内存中删除,并且释放对对象的加锁。
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static IdGenerator instance;
private static SharedObjectStorage storage = FileSharedObjectStorage(/*入参省略*/);
private static DistributedLock lock = new DistributedLock();
private IdGenerator() {}
public synchronized static IdGenerator getInstance()
if (instance == null) {
lock.lock();
instance = storage.load(IdGenerator.class);
}
return instance;
}
public synchroinzed void freeInstance() {
storage.save(this, IdGeneator.class);
instance = null; //释放对象
lock.unlock();
}
public long getId() {
return id.incrementAndGet();
}
}