单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例模式包含懒汉式和饿汉式,运行有且仅有一个实例化对象,只会new一次,两者区别在于何时new一个对象
原理:
如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造方法的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
实例化对象的创建要消耗大量的时间和资源
在整个软件系统运行过程中,这个类只被实例化一次,以后不论在哪都只调用这一个实例
要求:
- 掌握五种单例模式的实现方式
- 理解为何 DCL 实现时要使用 volatile 修饰静态变量
- 了解 jdk 中用到单例的场景
饿汉式
在类加载之后先通过new关键字创建一个对象,后续调用getInstance()
方法时直接返回该对象
public class Singleton implements Serializable {// 构造方法私有化,不能通过new关键字来创建对象private Singleton() {// 构造方法抛出异常是防止反射破坏单例if (INSTANCE != null) {throw new RuntimeException("单例对象不能重复创建");}System.out.println("private Singleton()");}// 私有,静态,不可变private static final Singleton INSTANCE = new Singleton();public static Singleton getInstance() {return INSTANCE;}public static void otherMethod() {System.out.println("otherMethod()");}// 防止反序列化破坏单例public Object readResolve() {return INSTANCE;}
}
枚举饿汉式
枚举饿汉式能天然防止反射、反序列化破坏单例
public enum Singleton {INSTANCE;private Singleton() {System.out.println("private Singleton()");}@Overridepublic String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());}public static Singleton getInstance() {return INSTANCE;}public static void otherMethod() {System.out.println("otherMethod()");}
}
懒汉式
在第一次调用getInstance()
方法时通过new创建对象,以后再次调用该方法时,直接返回第一次调用时创建的对象
public class Singleton implements Serializable {private Singleton() {System.out.println("private Singleton()");}private static Singleton INSTANCE = null;// 同步执行,避免线程问题public static synchronized Singleton getInstance() {if (INSTANCE == null) {INSTANCE = new Singleton();}return INSTANCE;}public static void otherMethod() {System.out.println("otherMethod()");}}
其实只有首次创建单例对象时才需要同步,但该代码实际上每次调用都会同步,因此有了下面的双检锁改进
双检锁懒汉式
public class Singleton implements Serializable {private Singleton() {System.out.println("private Singleton()");}private static volatile Singleton INSTANCE = null; // 可见性,有序性public static Singleton getInstance() {if (INSTANCE == null) {synchronized (Singleton.class) {if (INSTANCE == null) {INSTANCE = new Singleton();}}}return INSTANCE;}public static void otherMethod() {System.out.println("otherMethod()");}
}
为何必须加 volatile:
INSTANCE = new Singleton4()
不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造- 如果线程1 先执行了赋值,线程2 执行到第一个
INSTANCE == null
时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象
内部类懒汉式
public class Singleton implements Serializable {private Singleton() {System.out.println("private Singleton()");}private static class Holder {static Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return Holder.INSTANCE;}public static void otherMethod() {System.out.println("otherMethod()");}
}
- 避免了双检锁的缺点
实例
JDK 中单例的体现
- Runtime 体现了饿汉式单例
- Console 体现了双检锁懒汉式单例
- Collections 中的 EmptyNavigableSet 内部类懒汉式单例
- ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
- Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例