1、什么是单例模式?
“确保一个类只有一个实例,并且提供一个访问它的全局方法。”
2、常见使用实例
jdk Runtime类
该类用于管理应用程序运行期间的各种信息,比如内存信息、环境变量等。
3、单例模式的两种类型及代码实现
3.1 饿汉式
在类初始化时,就进行了实例化。很多人认为饿汉式的缺点是在启动过程中就初始化完成,占用资源大。但是从另一方面来想,如果这个初始化耗时长,在我们的接口使用它的时候才去初始化,那么也会造成接口响应时间过长。所以这也不能完全算是缺点,还是需要权衡。
3.1.1 标准饿汉
// 方式一 实例是公有的,可直接通过Singleton.instance使用
public static class Singleton {
public static final Singleton instance = new Singleton();
private Singleton() {
}
}
// 方式二 实例是私有的,对外提供公有的getInstance()方法来获取实例
public static class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
推荐使用第二种方式。
因为第二种方式,更加灵活。
假如我们现在有一个需求,要求该类为每一个线程生成一个实例,那么使用方式二我们就可以保留getInstance()这个方法,只需要修改方法的具体实现,更加灵活。
3.1.2 枚举类(饿汉单例最推荐使用的,原因在后面分析)
创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量)。
public enum Single {
SINGLE;
private Single() {
}
public void print() {
System.out.println("hello world");
}
}
3.2 懒汉式
在第一次调用的时候进行实例化
3.2.1 简单单线程
当instance为null时,多个线程同时进入判断,导致会创建多个实例。
public static Singleton1 getInstanceA() {
if (null == instance) {
instance = new Singleton1();
}
return instance;
}
3.2.2 加synchronized
多线程环境下能保证创建一个实例,但是效率低。
public static synchronized Singleton1 getInstanceB() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
3.2.3 双重校验锁
第一个if判断是为了提高效率的,如果实例已经被创建了,那么就不需要加锁。
// 增加volatile关键字,防止指令重排序
private static volatile Singleton1 singleton1;
public static Singleton1 getInstanceC() {
// 先判断实例是否存在,若不存在再对类对象进行加锁处理
if (instance == null) {
synchronized (Singleton1.class) {
if (instance == null) {
instance = new Singleton1();
}
}
}
return instance;
}
3.2.4 静态内部类
class SingletonStaticInnerSerialize{
private static class InnerClass {
private static SingletonStaticInnerSerialize singletonStaticInnerSerialize = new SingletonStaticInnerSerialize();
}
public static SingletonStaticInnerSerialize getInstance() {
return InnerClass.singletonStaticInnerSerialize;
}
}
3.2.5 防止反序列化创建多实例的静态内部类
在反序列化时,若目标类有readResolve方法,那就通过反射的方式调用要被反序列化的类中的readResolve方法,返回一个对象。否则就创建一个对象。
//使用匿名内部类实现单例模式,在遇见序列化和反序列化的场景,得到的不是同一个实例
//解决这个问题是在序列化的时候使用readResolve方法
class SingletonStaticInnerSerialize implements Serializable {
private static final long serialVersionUID = 1L;
private static class InnerClass {
private static SingletonStaticInnerSerialize singletonStaticInnerSerialize = new SingletonStaticInnerSerialize();
}
public static SingletonStaticInnerSerialize getInstance() {
return InnerClass.singletonStaticInnerSerialize;
}
// 关键!!!!
protected Object readResolve() {
return InnerClass.singletonStaticInnerSerialize;
}
}
3.3 总结与对比
| 初始化时机 | 是否线程安全 | 是否支持延迟加载 | |
|---|---|---|---|
| 标准饿汉 | 类加载期间 | 是 | 否 |
| 枚举 | 类加载期间 | 是 | 否 |
| 简单单线程 | 第一次使用时 | 否 | 是 |
| synchronized | 第一次使用时 | 是 | 是 |
| 双重校验锁 | 第一次使用时 | 是 | 是 |
| 静态内部类 | 这个内部类在其静态成员被调用时发生 | 是 | 是 |
推荐使用'枚举'、'双重校验锁'、'静态内部类'这三种方式。