前言
单例模式是最常用到的设计模式之一,比如封装网络请求框架时,RxJava+Retrofit的封装过程就用到了单例模式。顾名思义,保证一个类只有一个实例,并提供一个访问它的全局访问点。也就是说,要确保这个类只有一个对象实例。
思路
单例模式要求类有一个方法用来返回对象的一个引用和获取该实例,必须是静态方法,一般使用getInstance。单利模式的实现分两步走:
- 1.把类的构造定义为私有方法,这样别人就无法调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例
- 2.类提供一个静态方法,若被调用,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用
单例模式的使用场景
单例模式的使用场景如下:
- 1.整个项目需要一个共享访问点或共享数据
- 2.创建一个对象需要效果贼多资源,比如访问I/O、数据库、线程池
- 3.工具类对象
注意:
- 1.构造函数不对外开放,一般为private
- 2.通过一个静态方法或者枚举来返回单例类对象
- 3.确保单例类的对象有且只有一个,尤其是在多线程的环境下
- 4.确保单例类对象在反序列化时不会重新构建对象
单例类的构造函数私有化,可以保证Client不能通过new的方式创建对象。单例类会暴露一个公有的静态方法,Client只能调用该静态方法才能获取单例类唯一的对象,且在该过程中需要保证线程安全。
单例模式的写法
1.懒汉式-线程不安全写法
懒汉模式声明了一个静态对象,在第一次调用时初始化。虽然节约了资源,但是第一次加载时要初始化,反应要慢,且多线程下不能正常工作,因为一旦线程A进入了if语句中,还未来得及继续走,线程B也同时进来了,这样就不能保证单例了。
public class Singleton {
private static Singleton singleton;//声明一个静态对象
//私有构造,保证不能通过调用构造方法来实例化对象
private Singleton(){}
public static Singleton getInstance(){
if (singleton==null){
//第一次调用才会进行初始化,起到了lazy load的效果
singleton=new Singleton();
}
return singleton;
}
// private Object readResolve() throws ObjectStreamException{
// return singleton;
// }
}
调用只需要Singleton instance = Singleton.getInstance();
优点:延迟加载,需要的时候才会初始化实例对象
缺点:线程不安全,不可用
2.懒汉式-线程安全写法
加入了synchronized锁,并且支持延迟加载(lazy load)。但正是因为有锁,效率低下。每个线程获取类实例的时候,执行getInstance()方法都要进行同步,造成了不必要的同步开销。况且大部分情况下,我们都不需要同步。
public class Singleton {
private static Singleton singleton;//声明一个静态对象
private Singleton(){}
public static synchronized Singleton getInstance(){
if (singleton==null){
//第一次调用才会进行初始化
singleton=new Singleton();
}
return singleton;
}
// private Object readResolve() throws ObjectStreamException{
// return singleton;
// }
}
优点:既保证了线程安全,又能实现lazy load延迟加载
缺点:在多线程中每次要获取实例对象都要先进行不必要的同步,效率低下。
3.懒汉式-线程安全同步代码块
同步方法效率太低,优化为同步代码块。但是这种方式依旧起不到线程同步作用,2个线程同时进入if条件语句,就会产生多个实例。
public class Singleton {
private static Singleton singleton;//声明一个静态对象
private Singleton(){}
public static Singleton getInstance(){
if (singleton==null){
//同步代码块,依旧起不到同步的作用
synchronized (Singleton.class){
singleton=new Singleton();
}
}
return singleton;
}
// private Object readResolve() throws ObjectStreamException{
// return singleton;
// }
}
优点:既保证了线程安全,又能实现lazy load延迟加载
缺点:未起到同步作用
4.饿汉式-静态常量
public class Singleton {
private final static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
// private Object readResolve() throws ObjectStreamException{
// return singleton;
// }
}
优点:类装载就完成了实例化,避免了同步问题
缺点:没起到lazy load效果
5.饿汉式-静态代码块
public class Singleton {
private static Singleton singleton;
static {
singleton=new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
// private Object readResolve() throws ObjectStreamException{
// return singleton;
// }
}
优点:类装载就完成了实例化,避免了同步问题
缺点:没起到lazy load效果
6.双重检查模式DCL
使用volatile 修饰,起到了禁止指令重排序和内存可见性的作用。资源利用率高,首次调用getInstance方法才会去创建实例,但是首次加载速度慢。
public class Singleton {
private volatile static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if (singleton==null){ //为了不必要的同步
synchronized (Singleton.class){
if (singleton==null){ //singleton为null才会创建实例
singleton = new Singleton();
}
}
}
return singleton;
}
// private Object readResolve() throws ObjectStreamException{
// return singleton;
// }
}
优点:资源利用率高,第一次获取实例才会去创建
缺点:首次获取实例时间长,且存在高并发环境下造成的DCL失效
7.静态内部类
第一次加载Singleton类并不会初始化singleton,只有当虚拟机第一次调用getInstance方法加载SingletonHolder时才会初始化singleton。既保证线程安全,也使实例唯一。
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.singleton;
}
public static class SingletonHolder{
private static final Singleton singleton = new Singleton();
}
// private Object readResolve() throws ObjectStreamException{
// return singleton;
// }
}
优点:线程安全、延迟加载lazy load、效率高
8.枚举
默认枚举创建是线程安全,且任何情况下都是单例。以上的单例模式创建,反序列化会重新创建对象:将一个单例对象写到磁盘,再读回来,从而获取到一个实例对象。反序列化提供了readResolve方法(控制对象的反序列化)。要想杜绝反序列化重新生成对象,就得把注释打开。
public enum Singleton{
INSTANCE;
public void doSomeThing(){
//...
}
}
优点:默认线程安全、单例,简单明了
缺点:很少用,可读性不高