Java 中单例模式是一种常见的设计模式,几乎 设计模式面试题中必问的,开始之前我们先了解一下为什么需要单例模式。
为什么需要单例模式
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态。
单例模式特点
-
单例类只能有一个实例;
-
单例类必须自己创建自己的唯一实例;
-
单例类必须给所有其他对象提供这一实例。
单例模式的写法有好几种,这里介绍常用的几种单例模式:懒汉式单例、饿汉式单例、内部类式、枚举式、双重校验式单例。
-
第一种(懒汉,线程不安全):
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
写法评价:
优点:延迟加载很明显;
缺点:致命缺点的是线程不安全,在多线程不能正常工作。
-
第二种(懒汉,线程安全):
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
写法评价:
优点:线程安全,多线程中很好的工作,而且看起来它也具备很好的延迟加载;
缺点:效率很低,实际99%情况下不需要同步。
-
第三种(饿汉):
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
写法评价:
优点:基于classloder 机制避免了多线程的同步问题,线程安全;
缺点:初始化 instance 显然没有达到延迟加载的效果。原因是导致类装载的情况有很多种,在单例模式中我们期望的是调用 getInstance 方法初始化 instance, 但是我们不能确定有其他的方式也导致类的装载如调用其他的静态方法。
-
第四种(饿汉,变种):
public class Singleton { private Singleton instance = null; static { instance = new Singleton(); } private Singleton (){} public static Singleton getInstance() { return this.instance; } }
写法评价:
优点:使用静态代码块初始化实例,线程安全;
缺点:加载时机其实和第三种方式差不多,都是在类初始化即实例化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; } }
写法评价:
线程安全,利用了classloder的机制来保证初始化instance时只有一个线程;延迟加载,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,再回初始化 Singleton 类。(第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化)
-
第六种(枚举):
public enum Singleton { INSTANCE; public void whateverMethod() { } }
写法评价:
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
-
第七种(双重校验锁):
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
写法评价:
使用 volatile 保证 instance 在线程间的可见性,在调用 getInstance 实例化时,使用 synchronized 锁住类模板,保证线程安全,在 synchronized 代码块中使用双重校验机制保证类只会被实例化一次,达到线程安全和延迟加载的特性。