一文完全解读单例模式

前言

单例模式是一种设计模式,用于将类的实例化限制为一个对象。这意味着只能创建该类的一个实例,任何创建其他实例的尝试都将返回对第一个实例的引用。这在只拥有一个类的一个实例很重要的情况下很有用,例如在管理对共享资源的访问或控制对全局资源的访问时。

单例模式的一个常见用例是管理对数据库连接池的访问。在这种情况下,单例类将管理连接池的创建和初始化,并为其他类提供从池中请求连接的方法。这样可以确保只有一个连接池,有助于防止资源耗尽并提高性能。

单例模式的另一个常见用例是控制对共享资源的访问,例如日志类或配置对象。在这些情况下,单例类将管理共享资源的初始化和配置,并为其他类提供访问和使用资源的方法。这确保所有类都使用相同的共享资源,这有助于防止不一致并改进整体系统行为。

一般而言,单例模式在需要恰好拥有一个类的一个实例并控制对该实例的访问时非常有用。它可以帮助提高性能并确保在需要管理共享资源或全局状态的情况下的一致性。

实现的几种方式

使用静态字段来保存实例:

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();
}
复制代码

通过采取这些步骤,可以使单例模式对破坏或绕过它的尝试更具弹性。

哪些情况下可以使用单例模式

单例模式的其他一些潜在用例包括实现全局事件总线或消息传递系统、管理对线程池或其他共享并发资源的访问,以及实现全局注册表或查找服务以管理不同组件之间的关系系统。在每一种情况下,单例模式都可用于确保只有一个相关类的实例,并为其他类提供一个中心访问点以使用资源或与系统交互。

除了这些特定的用例之外,单例模式在更一般的情况下也很有用,在这些情况下,对系统中的关键资源或全局状态进行单点访问很重要。例如,该模式可用于实现全局状态管理器或为面向服务的体系结构提供中央访问点。在这些情况下,单例模式可以帮助提高性能、确保一致性,并通过提供对相关资源的单点访问来简化复杂系统的管理

单例模式不适用的情况

单例模式并非适用于所有情况,在某些情况下使用该模式可能会对软件系统的设计产生不利影响。例如,该模式会使测试依赖于单例类的代码变得困难,因为不可能用模拟或存根对象替换单例实例。这可能会使编写单元测试来充分执行代码并验证其行为变得困难。

此外,单例模式会使软件系统随着时间的推移难以维护和发展。由于单例类控制对关键资源或全局状态的访问,因此对类进行更改会对系统的其余部分产生深远的影响。这使得在不破坏系统其他部分的情况下修改单例类变得困难,并且也使得添加依赖于单例类的新特性或功能变得困难。

总结

总的来说,应该谨慎使用单例模式,并且只有在明确需要类的单个实例和该实例的定义良好的访问点时才使用。在模式不适合的情况下,最好使用不同的设计模式或完全避免使用该模式。

之前面试中比较容易问到的是怎么破坏单例模式 和 解决办法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
责任链设计模式是一种行为型设计模式,用于将请求的发送者和接收者解耦,使多个对象都有机会处理该请求。该模式将这些对象串成链,并沿着这条链传递请求,直到有一个对象能够处理它为止。 责任链模式的核心是定义一个处理请求的抽象类或接口,然后让多个具体的处理者对象继承或实现这个类/接口。每个具体的处理者对象都包含一个对下一个处理者对象的引用,形成一个链式结构。 当一个请求进入责任链时,责任链中的每个处理者都有机会处理该请求。如果可以处理请求,则进行处理;如果不能处理,则将请求传递给下一个处理者,直到有一个处理者能够处理它。 责任链模式的关键点是要找到合适的处理者顺序和条件。通常情况下,责任链模式适用于以下情况: 1. 有多个对象可以处理同一类型的请求,但具体由哪个对象来处理由运行时决定。 2. 不明确请求的接收者,希望请求在一个对象链中流动,直到被处理。 3. 需要动态地指定可以处理请求的对象集合。 使用责任链模式可以实现请求发送者和接收者的解耦,增加代码的灵活性和可扩展性。但同时也需要注意责任链的长度和效率问题,避免责任链过长或造成性能问题。 总结一下,责任链设计模式是一种将请求发送者和接收者解耦的设计模式,通过将多个处理者对象串成链,沿着这条链传递请求,直到有一个处理者能够处理它。这样可以增加代码的灵活性和可扩展性,适用于有多个对象可以处理同一类型请求的情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值