设计模式--单例模式
单例模式
一个类只能有一个对象。该类自己负责创建对象,同时确保只有一个对象被创建。
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给其他所有类提供这一实例
使用场景:
- 要求产生唯一的序列号
- WEB中的技术去,不用每次刷新都在书库里加一次。用单例先缓起来
- 创建的一个对象需要消耗的资源过多,比如I/O与数据库的连接等。
实现方法
1.懒汉式
Lazy初始化: 否
线程安全: 否
描述: 最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
具体实现
//懒汉式线程安全做法为加锁,在getInstance方法上加sychronized锁。但是这样会影响效率
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
//doSomething() 该实例支持的行为
}
2.饿汉式
Lazy初始化: 否
线程安全: 是
描述: 常用方法,但是容易产生垃圾对象。基于classloader机制避免了多线程的同步问题。在类加载时就实例化对象,没达到lazy loading效果
- 优点:没有加锁,效率高
- 缺点:类加载时就初始化,可能会产生垃圾
具体实现
public class Singleton{
private static Singleton instance=new Singleton();
private Singleton(){};
public static Singleton getInstance(){
return instance;
}
//doSomething() 该实例支持的行为
}
3.双检锁/双重校验锁(DCL)
JDK版本: 1.5起
Lazy初始化: 是
多线程安全: 是
描述: 采用双锁机制,安全且在多线程情况下能保持高性能。getInstance()的性能对程序很关键。
具体实现
public class Singleton{
//当singleton不为null时通知想要实例化的其他线程
private volatile static Singleton singleton;
private Singleton(){};
public Singleton getInstance(){
//判断是否为空,为空则获得sychronized锁
if(instance==null){
sychronized(Singleton.class);
//获得sychronized锁后再次判断,防止获得锁时,已经被实例化。
if(singleton==null){
singleton=new Singleton();
}
}
return singleton;
}
//doSomething() 该实例支持的行为
}
登记式/静态内部类
Lazy初始化: 是
线程安全: 是
描述: 能达到双检锁一样的功效。但是实现更简单。本方法采用classloader机制来确保初始化时只有一个线程。同饿汉式不同,饿汉式只要类被加载了,instance一定被实例化。本方法只有通过主动调用getInstance()来显示装在SingletonHolder,从而实例化instance
具体实现
public class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANCE =new Singleton();
}
private Singleton(){};
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
//doSomething() 该实例支持的行为
}
6.枚举
JDK版本: 1.5起
是否Lazy初始化: 否
多线程安全: 是
描述: 实现单例模式的最佳方法。简洁,而且支持序列化机制,绝对防止多次被实例化。代码可读性差
具体实现
public enum Singleton{
INSTANCE
//doSomething() 该实例支持的行为
//可以省略此方法,通过Single.INSTANCE进行操作
public static Singleton getInstance(){
return Singleton.INSTANCE
}
}
总结
枚举式不常用,可读性差。
既要求线程安全,又要求Lazy加载使用双检锁式。
要求线程安全,不要求Lazy加载使用饿汉式。
注意: 前五种方法可能会多次实例化,当实例被写入到文件到反序列化成实例时,会重复实例化。为了避免这种情况。如果涉及到序列化、反序列化时,我们需要重写readResolve方法,以让实例唯一。
private Object readResolve() throws ObjectStreamException{
return singleton;
}