概述
单例模式,是指某个类只允许生成一个实例,这个实例由自己实例化并提供给外界一个全局访问点。
使用场景
一些比较消耗资源的类,如网络请求、线程池、缓存等,为了避免创建多个对象消耗更多的资源,需要采用单例模式。
实现方式
1.饿汉式
package com.jyq.instance;
public class HeadTeacher extends Teacher {
//创建一个私有的、静态的、final的全局对象
private static final HeadTeacher mHandTeacher = new HeadTeacher();
//构造函数私有化
private HeadTeacher() {
}
//通过静态方法返回该对象实例
public static HeadTeacher getInstance() {
return mHandTeacher;
}
}
当JVM加载该类时,就马上创建该类唯一的实例,因此得名饿汉式(eagerly instance).饿汉式也是实现单例模式的推荐方式。
2.懒汉式
package com.jyq.instance;
public class HeadTeacher extends Teacher {
//声明一个私有的、静态的全局对象
private static HeadTeacher mHandTeacher;
//构造函数私有化
private HeadTeacher() {
}
//通过静态方法返回该对象实例
public static HeadTeacher getInstance() {
if (mHandTeacher == null) {
mHandTeacher = new HeadTeacher();
}
return mHandTeacher;
}
}
由于只有在第一次调用getInstance()
后才会创建该类唯一实例,因此得名懒汉式(lazy instance),这种实例化方式也叫作延时初始化。
改进懒汉式
然而,懒汉式的缺陷显而易见,如果存在多个线程同时访问getInstance()
方法,则很有可能返回多个实例,这显然违背了单例模式的初衷。因此,需要解决多线程安全问题,通过synchronized
关键字进行同步:
public static synchronized HeadTeacher getInstance() {
if (mHandTeacher == null) {
mHandTeacher = new HeadTeacher();
}
return mHandTeacher;
}
现在getInstance()
变成了同步方法,因此,解决了多线程安全问题。
然而,虽然通过同步的方式解决了多线程安全问题,但是又引入了一个问题:每次不同的线程调用getInstance()
都会进行同步,同步一个方法必然会造成执行效率下降。
因此,如果对于性能要求不高,则可以使用这种方式,如果getInstance()
使用频率高,那么就需要考虑其他方式实现了。
3.Double Check Locking(DCL)
在懒汉式中说到,每次调用getInstance()
方法对性能影响很大,实际上,只需要在第一次执行getInstance()
方法时进行同步即可:
public class HeadTeacher extends Teacher {
private static volatile HeadTeacher mHandTeacher;
//构造函数私有化
private HeadTeacher() {
}
//通过静态方法返回该对象实例
public static HeadTeacher getInstance() {
if (mHandTeacher == null) {
synchronized (HeadTeacher.class) {
if (mHandTeacher == null) {
mHandTeacher = new HeadTeacher();
}
}
}
return mHandTeacher;
}
}
这种实现方式就称为双重检查锁,相比于懒汉式,这种方式避免了在唯一实例被初始化后访问该对象的开销,在第一检查时没有使用同步锁,第二次检查时使用同步锁,从而保证了只有在第一次实例化该类的唯一对象时才会进行同步,大大提高了程序性能。
volatile
关键字可以保证任何一个线程读取对象的时候得到最近被写入的值。
不过这种方式也有其缺点,在JDK1.5之前,由于volatile
关键字的语义不够强,会导致DLC的不稳定从而失效。在JDK1.5之后,引入了JMM(java 内存模型)解决了这个问题。同时volatile
关键字也会或多或少地影响性能。
4.静态内部类单例模式
这种方式示例如下:
package com.jyq.instance;
public class HeadTeacher extends Teacher {
//构造函数私有化
private HeadTeacher() {
}
//通过静态方法返回该对象实例
public static HeadTeacher getInstance() {
return HeadTeacherHolder.mInstance;
}
private static class HeadTeacherHolder {
private static final HeadTeacher mInstance = new HeadTeacher();
}
}
这种初始化方式也叫作Initialize-On-Demand Holder Class and Singletons
模式,当第一次加载HeadTeacher 类时,并不会实例化唯一实例,只有在调用getInstance()
时,唯一实例才会被初始化。
5.使用单元素枚举
枚举类型是指由一组固定常量组成的合法值的类型。除了不能继承枚举之外,基本上可以将enum看做一个常规类,因此,我们可以在枚举中添加构造方法、成员方法。同时,enum还有以下特点:
- 1.线程安全;
- 2.实现了Serializeable接口,自动提供了序列化机制,,任何情况下都保证了对象的唯一性。
为什么说枚举类型在任何情况下都保证了对象的唯一性呢?这是因为无论采用以上何种方式获取唯一实例,在对该实例进行序列化和反序列化操作后,都会生成新的实例,而如果要保证在任何情况下都维持唯一实例,除了implements Serializable
之外,还需要提供readResolve()
方法:
private Object readResolve {
return mInstance;
}
使用单元素枚举方式示例如下:
public enum HeadTeacher {
INSTANCE;
public void show() {
System.out.println("Enum show...");
}
}
不仅需要更少的代码量,而且目前单元素枚举类型已经成为实现单例模式的最佳方法。
总结
- 1.懒汉式、DLC模式、静态内部类模式,实际上都是表现为一种行为:延迟初始化(Lazy Initialization)。应尽可能少得使用延时初始化,它是一把双刃剑,好处是降低了创建实例的开销,坏处是当在多线程的情况下,必须细致地保证多线程安全问题。
- 2.单元素的枚举类型是实现单例模式的最佳方式。