单例模式
概述
单例模式可以保证系统中一个类只有一个实例而且该实例易于被外界访问
单例模式有三个要点:
- 某个类只能有一个实例
- 自行创建这个实例
- 自行向整个系统提供这个实例
因此,单例类必须有如下实现:
- 单例类的构造函数为私有
- 提供一个自身的静态私有成员变量
- 提供一个公有的静态工厂方法
模式实例
public class Singleton {
private static Singleton instance = null;
//私有构造方法
private Singleton() {}
//静态公有工厂方法,返回唯一实例
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式单例类和懒汉式单例类
饿汉式单例类:在定义静态变量的时候实例化单例类
public class EagerSingleton {
// 可以由名知意,太饥饿了,一大早就准备好了实例等待使用,但很有可能一直没人使用
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
懒汉式单例类:在调用静态工厂方法时实例化单例类
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {}
public static LazySingleton getInstance() {
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
从资源利用效率角度来看,懒汉式单例类比饿汉式单例类稍好。从速度和反应时间角度来说,饿汉式单例类比懒汉式稍好。然而,懒汉式单例类在实例化时,必须处理好多个线程同时首次引用此类时的访问限制问题,就上述的懒汉式单例类实例来说,是线程不安全的
为了实现线程安全,可以加锁实现单例,但会影响效率
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {}
public static synchronized LazySingleton getInstance() {
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
双重校验锁懒汉模式(DCL)
由于 synchronized 具有排他性,同一时刻只有一个线程进入到同步代码块或者同步方法之中,效率很低。采用双锁机制,在保证安全的前提下还能保持高性能
public class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() {}
public static LazySingleton getInstance() {
// 当已经创建一个实例,再次调用就不需要进入同步代码块中了
if(instance == null) {
synchronized(LazySingleton.class) {
// 防止二次创建实例
if(instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
其中变量 instance 还使用了 volatile 修饰,使用 volatile 的目的是防止指令重排序以及保证变量在多线程运行时的可见性
静态内部类模式
这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
private static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
内部类(不论是静态内部类还是非静态内部类)都是在第一次使用时才会被加载,而且静态内部类的加载不需要依附外部类,在使用时才加载,因此只有当调用 SingletonHolder.INSTANCE 这条语句时才会初始化 INSTANCE。
那么会不会出现多个线程同时加载 SingletonHolder 类的情况呢?答案是不会。JVM 规定,如多个线程用到同一个类,而这个类还未被加载,则只有一个线程去加载类,其他线程等待。因此静态内部类模式的单例可以保证线程安全
枚举模式
枚举天然解决了多线程同步执行的问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
public enum Singleton {
INSTANCE;
public void method() {}
}