前言
单例模式是一种设计模式,用于将类的实例化限制为一个对象。这意味着只能创建该类的一个实例,任何创建其他实例的尝试都将返回对第一个实例的引用。这在只拥有一个类的一个实例很重要的情况下很有用,例如在管理对共享资源的访问或控制对全局资源的访问时。
单例模式的一个常见用例是管理对数据库连接池的访问。在这种情况下,单例类将管理连接池的创建和初始化,并为其他类提供从池中请求连接的方法。这样可以确保只有一个连接池,有助于防止资源耗尽并提高性能。
单例模式的另一个常见用例是控制对共享资源的访问,例如日志类或配置对象。在这些情况下,单例类将管理共享资源的初始化和配置,并为其他类提供访问和使用资源的方法。这确保所有类都使用相同的共享资源,这有助于防止不一致并改进整体系统行为。
一般而言,单例模式在需要恰好拥有一个类的一个实例并控制对该实例的访问时非常有用。它可以帮助提高性能并确保在需要管理共享资源或全局状态的情况下的一致性。
实现的几种方式
使用静态字段来保存实例:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
复制代码
使用静态方法创建和返回实例:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
复制代码
使用静态内部类来保存实例:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
复制代码
在所有这些实现中,该getInstance()
方法提供了对单例实例的全局访问点,单例实例是在第一次调用该方法时创建的。这允许其他类访问单例而无需创建它们自己的实例。
使用枚举来保存实例:
public enum Singleton {
INSTANCE;
public void doSomething() {
// Perform some action
}
}
复制代码
在这个实现中,单例实例被定义为一个枚举,它提供了一种简单高效的方式来实现单例模式。该doSomething()
方法可用于对单例实例执行某些操作。
使用同步方法创建和返回实例:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
复制代码
在这个实现中,getInstance()
方法是同步的,这确保了一次只有一个线程可以执行它。这在多线程环境中很有用,可以防止多个线程创建单例的多个实例。
使用双重检查锁定来创建和返回实例:
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
复制代码
在此实现中,该getInstance()
方法使用双重检查锁定来确保仅在需要时才创建单例实例。getInstance()
这可以通过避免每次调用时同步方法的开销来提供更好的性能。
破坏单例的几种方式
反射
反射可用于调用单例类的私有构造函数并创建一个新实例,绕过阻止创建多个实例的访问控制检查:
Singleton instance1 = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance();
复制代码
序列化和反序列化
序列化和反序列化可用于创建单例类的新实例:
Singleton instance1 = Singleton.getInstance();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(instance1);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Singleton instance2 = (Singleton) ois.readObject();
复制代码
克隆
克隆可用于创建单例类的新实例:
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = (Singleton) instance1.clone();
复制代码
为了防止这些和其他技术破坏单例模式,重要的是在单例类中实现clone()
、readResolve()
和writeReplace()
方法以维护单例实例的完整性。
怎么防止被上述方式破坏
私有化构造方法
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
复制代码
重写clone()
重写clone()
单例类中的方法,调用时抛出异常:
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
复制代码
重写readResolve()
重写readResolve()
单例类中的方法返回单例实例,这样反序列化就不会创建新的实例:
private Object readResolve() {
return getInstance();
}
复制代码
重写writeReplace()
重写writeReplace()
单例类中的方法返回单例实例,这样序列化就不会创建新实例了:
private Object writeReplace() {
return getInstance();
}
复制代码
通过采取这些步骤,可以使单例模式对破坏或绕过它的尝试更具弹性。
哪些情况下可以使用单例模式
单例模式的其他一些潜在用例包括实现全局事件总线或消息传递系统、管理对线程池或其他共享并发资源的访问,以及实现全局注册表或查找服务以管理不同组件之间的关系系统。在每一种情况下,单例模式都可用于确保只有一个相关类的实例,并为其他类提供一个中心访问点以使用资源或与系统交互。
除了这些特定的用例之外,单例模式在更一般的情况下也很有用,在这些情况下,对系统中的关键资源或全局状态进行单点访问很重要。例如,该模式可用于实现全局状态管理器或为面向服务的体系结构提供中央访问点。在这些情况下,单例模式可以帮助提高性能、确保一致性,并通过提供对相关资源的单点访问来简化复杂系统的管理
单例模式不适用的情况
单例模式并非适用于所有情况,在某些情况下使用该模式可能会对软件系统的设计产生不利影响。例如,该模式会使测试依赖于单例类的代码变得困难,因为不可能用模拟或存根对象替换单例实例。这可能会使编写单元测试来充分执行代码并验证其行为变得困难。
此外,单例模式会使软件系统随着时间的推移难以维护和发展。由于单例类控制对关键资源或全局状态的访问,因此对类进行更改会对系统的其余部分产生深远的影响。这使得在不破坏系统其他部分的情况下修改单例类变得困难,并且也使得添加依赖于单例类的新特性或功能变得困难。
总结
总的来说,应该谨慎使用单例模式,并且只有在明确需要类的单个实例和该实例的定义良好的访问点时才使用。在模式不适合的情况下,最好使用不同的设计模式或完全避免使用该模式。
之前面试中比较容易问到的是怎么破坏单例模式 和 解决办法