单例模式(Singleton Pattern)限制系统中某一个类只能有一个唯一的实例。很多时候系统对类的需求就只是一个全局对象,有些资源比较重,加载创建耗时,适用于单例模式;有些资源代表的是纯函数的操作,虽然可以使用new 来创建新对象,使用单例模式可以减少对象创建消耗,在手机等资源少的地方推荐使用。
通常的单例获取是类提供一个 getInstance
的静态方法,外部对象使用静态方法获取到类实例。
在类加载后直接实例化单例,系统控制并发,缺点是如果这个类从始至终都没有被使用过,加载实例浪费了系统资源。
public final class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
针对预加载可能浪费资源的问题,可以使用懒加载来避免。懒加载只有在资源被使用到的时候才会去加载。
public final class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
如果存在多线程并发的情况,上面的获取实例方法存在竞态条件,有两种解决方案。
synchronized
同步方法public final class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
由于加了同步,因此多线程访问时只能串行访问,牺牲了效率保证了安全。
public final class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查保证了只有实例还未初始化时才会竞争锁,当实例初始化后,与第一种懒加载方式执行路径是一样的,并且减少了锁争抢开销。(jdk1.5 之后才可以使用)
懒加载虽然保证了资源加载就一定是被使用的,但是如果资源加载时间较长,所以使用方都必须等待资源加载完成,譬如数据库的加载、连接建立,因此要看自己的使用场景来判断是否需要使用懒加载模式。
静态内部类严格说起来属于懒加载,使用了jvm 加载类的机制,避免了使用双重检测的方式来防止并发问题。
public final class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
return Holder.holder;
}
private static class Holder {
private static Singleton holder = new Singleton();
}
}
在 Singleton
类加载时内部类并没有被加载,因此内部类的 holder
未被实例化,调用 getInstance
方法时,才真正加载和实例化。
枚举天生自带的单一属性非常符合单例模式,使用枚举的解决方案也非常优雅,而且还适合与策略、命令等模式配合。
枚举相比于前面的实现有几个优点:
private
,而且设为了 private
也无法阻止使用方使用反射来创建实例。readObject
writeObject
方法。当然枚举也是一种预加载的实现方案,适合简单的单例,对于复杂的单例实现像数据库引擎等的还是前面的方式更合适一些。
public enum Singleton {
/**
* 实例
*/
INSTANCE;
/**
*
*/
public void doSomething() {
}
}
public enum SingletonEnum {
/**
* 实例
*/
INSTANCE1 {
@Override
public void forOverride() {
System.out.println("Instance1");
}
},
INSTANCE2 {
@Override
public void forOverride() {
System.out.println("Instance2");
}
};
/**
*
*/
public void doSomething() {
}
public abstract void forOverride();
}
单例模式在后端自己创建的场景已经不多了,Spring 框架完全代理了相关操作,为开发者提供了很大的遍历,但是 Spring 本质上是一个工厂,有些复杂场景还是需要自己来动手。