单例设计模式

介绍

单例模式确保一个类只有一个实例,并提供了一个全局访问点。

应用

线程池,数据库连接对象。

经典单例模式

一个经典的单例模式实现:

// NOTE: This is not thread safe!

public class Singleton {
private static Singleton uniqueInstance;

private Singleton() {}

public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}

该实现是线程不安全的。可以想象有两个线程同时进入了getInstance()方法。

解决经典单例模式线程安全问题

解决方案,给getInstance方法加上synchronized关键字,迫使每个线程进入该方法前都需要等待别的线程离开该方法。

public class Singleton {
private static Singleton uniqueInstance;

private Singleton() {}

public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}

该解决方案得缺点就是只有第一次执行该方法才真正需要线程同步。

更进一步…

这里给出三种改善方案:

  1. 如果getInstance得性能对于应用程序不是很关键,就什么也别做。
  2. 使用“急切”创建实例,而不用延迟实例化的做法。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public Singleton getInstance(){
return instance;
}
}
  1. 使用双重检查加锁,在getInstance中减少使用同步。
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public Singleton getInstance(){
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

备注:

  • 首先检查实例是否存在,如果不存在再进入同步代码块。
  • 进入区块后在检查一次,如果仍然是null则创建实例。
  • volatile关键字确保:当instance变量初始化后,多个线程正确的处理instance变量。

思考

两个类加载器可能有机会创建自己的单例实例?

是的。所以如果你的程序有多个类加载器又同时使用了单例模式,请小心。解决方法是自行指定类加载器,并指定同一个类加载器。

类如果能做两件事,就违反了OO设计。单例模式是否违反了这样的观念呢?

单例类不止负责管理自己的实例,还在应用程序中担任角色,所以可以视为是两个责任。但是由类管理自己的实例的做法并不少见,也可以让设计更简单。

我想把单例类当成超类,设计出子类。究竟可不可以继承单例类。

不能。继承单例类遇到的一个问题就是构造器是私有的。你不能用私有构造器来扩展类。

参考资料

  • 《Head First设计模式》(中文版)
文章作者: Met Guo
文章链接: https://guoyujian.github.io/2021/12/16/%E5%8D%95%E4%BE%8B%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Gmet's Blog