饿汉式是相对于懒汉式来说的,懒汉式是第一次调用 getInstance() 方法时,才创建实例,而饿汉式则是不调用 getInstance() 方法,类初始化时,实例会被提前创建出来
- // 1. 饿汉式
- public class Singleton1 implements Serializable {
-
- // 构造私有
- private Singleton1() {
- System.out.println("private Singleton1()");
- }
-
- // 静态成员变量
- private static final Singleton1 INSTANCE = new Singleton1();
-
- // 公共静态方法
- public static Singleton1 getInstance() {
- return INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
- }
- public class TestSingleton {
- public static void main(String[] args) throws Exception {
- Singleton1.otherMethod();
- System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
-
- // 调用两次,看看是否是同一实例
- System.out.println(Singleton1.getInstance());
- System.out.println(Singleton1.getInstance());
-
- }
- }
-
- 控制台输出:
- private Singleton1()
- otherMethod()
- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- com.ee.dd.mal.rest.Singleton1@782830e
- com.ee.dd.mal.rest.Singleton1@782830e
查看控制台输出:
com.ee.dd.mal.rest.Singleton1@782830e
com.ee.dd.mal.rest.Singleton1@782830e
说明是同一对象;
调用静态方法 otherMethod(),可以触发 Singleton1 的初始化操作,类初始化操作时,会调用构造方法,单例对象会被创建,getInstance() 时,拿到的是已经创建好的对象;
- // 饿汉式
- public class Singleton1 implements Serializable {
-
- // 构造私有
- private Singleton1() {
- System.out.println("private Singleton1()");
- }
-
- // 静态成员变量
- private static final Singleton1 INSTANCE = new Singleton1();
-
- // 公共静态方法
- public static Singleton1 getInstance() {
- return INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
- }
- public static void main(String[] args) throws Exception {
- Singleton1.otherMethod();
- System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
-
- // 调用两次,看看是否是同一实例
- System.out.println(Singleton1.getInstance());
- System.out.println(Singleton1.getInstance());
-
- // 反射破坏单例
- reflection(Singleton1.class);
- }
-
- private static void reflection(Class> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
- Constructor> constructor = clazz.getDeclaredConstructor(); // 拿到一个无参的构造方法
- constructor.setAccessible(true); // 设置私有的构造方法,也可以被使用
- System.out.println("反射创建实例:" + constructor.newInstance()); // 调用构造方法的 newInstance() 也可以创建实例
- }
-
- 控制台输出:
- private Singleton1()
- otherMethod()
- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- com.qq.ww.mal.rest.Singleton1@782830e
- com.qq.ww.mal.rest.Singleton1@782830e
- private Singleton1()
- 反射创建实例:com.hbis.ttie.mal.rest.Singleton1@470e2030
可以看到,getInstance() 拿到一个对象,通过反射,构造方法的信息输出了出来,创建了另一个对象,一个类,两个对象,就不是单例了
如何预防?
修改代码
- // 饿汉式
- public class Singleton1 implements Serializable {
-
- // 构造私有
- private Singleton1() {
- if (INSTANCE != null) {
- throw new RuntimeException("单例对象不能重复创建");
- }
- System.out.println("private Singleton1()");
- }
-
- // 静态成员变量
- private static final Singleton1 INSTANCE = new Singleton1();
-
- // 公共静态方法
- public static Singleton1 getInstance() {
- return INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
- }
- public static void main(String[] args) throws Exception {
- Singleton1.otherMethod();
- System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
-
- // 调用两次,看看是否是同一实例
- System.out.println(Singleton1.getInstance());
- System.out.println(Singleton1.getInstance());
-
- // 反射破坏单例
- reflection(Singleton1.class);
- }
-
- private static void reflection(Class> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
- Constructor> constructor = clazz.getDeclaredConstructor(); // 拿到一个无参的构造方法
- constructor.setAccessible(true); // 设置私有的构造方法,也可以被使用
- System.out.println("反射创建实例:" + constructor.newInstance()); // 调用构造方法的 newInstance() 也可以创建实例
- }
-
- 控制台输出:
- private Singleton1()
- otherMethod()
- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- com.qq.qq.mal.rest.Singleton1@782830e
- com.qq.qq.mal.rest.Singleton1@782830e
- Exception in thread "main" java.lang.reflect.InvocationTargetException
- at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
- at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
- at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
- at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
- at com.qq.qq.mal.rest.TestSingleton.reflection(TestSingleton.java:25)
- at com.qq.qq.mal.rest.TestSingleton.main(TestSingleton.java:19)
- Caused by: java.lang.RuntimeException: 单例对象不能重复创建
- at com.qq.qq.mal.rest.Singleton1.<init>(Singleton1.java:11)
- ... 6 more
发现反射调用的时候,报错了
如果单例对象实现了 Serializable 接口,单例可以被破坏
- // 饿汉式
- public class Singleton1 implements Serializable {
-
- // 构造私有
- private Singleton1() {
- if (INSTANCE != null) {
- throw new RuntimeException("单例对象不能重复创建");
- }
- System.out.println("private Singleton1()");
- }
-
- // 静态成员变量
- private static final Singleton1 INSTANCE = new Singleton1();
-
- // 公共静态方法
- public static Singleton1 getInstance() {
- return INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
- }
- public static void main(String[] args) throws Exception {
- Singleton1.otherMethod();
- System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
-
- // 调用两次,看看是否是同一实例
- System.out.println(Singleton1.getInstance());
- System.out.println(Singleton1.getInstance());
-
- // 反序列化破坏单例
- serializable(Singleton1.getInstance());
- }
-
- private static void serializable(Object instance) throws IOException, ClassNotFoundException {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(bos);
- oos.writeObject(instance); // 变成字节流
- ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
- System.out.println("反序列化创建实例:" + ois.readObject()); // 把字节流还原成对象,这是一个新的对象,并且不调用构造方法
- }
-
- 控制台输出:
- private Singleton1()
- otherMethod()
- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- com.qq.qq.mal.rest.Singleton1@782830e
- com.qq.qq.mal.rest.Singleton1@782830e
- 反序列化创建实例:com.hbis.ttie.mal.rest.Singleton1@2280cdac
查看控制台输出,创建了两个对象
如何预防?
- // 饿汉式
- public class Singleton1 implements Serializable {
-
- // 构造私有
- private Singleton1() {
- if (INSTANCE != null) {
- throw new RuntimeException("单例对象不能重复创建");
- }
- System.out.println("private Singleton1()");
- }
-
- // 静态成员变量
- private static final Singleton1 INSTANCE = new Singleton1();
-
- // 公共静态方法
- public static Singleton1 getInstance() {
- return INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
-
- // 方法名是固定的
- // 在反序列化时,发现重写了 readResolve() ,那么会利用 readResolve() 的返回值,作为结果返回
- public Object readResolve() {
- return INSTANCE;
- }
-
- }
- public static void main(String[] args) throws Exception {
- Singleton1.otherMethod();
- System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
-
- // 调用两次,看看是否是同一实例
- System.out.println(Singleton1.getInstance());
- System.out.println(Singleton1.getInstance());
-
- // 反序列化破坏单例
- serializable(Singleton1.getInstance());
- }
-
- private static void serializable(Object instance) throws IOException, ClassNotFoundException {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(bos);
- oos.writeObject(instance); // 变成字节流
- ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
- System.out.println("反序列化创建实例:" + ois.readObject()); // 把字节流还原成对象,这是一个新的对象,并且不调用构造方法
- }
-
- 控制台输出:
- private Singleton1()
- otherMethod()
- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- com.qq.qq.mal.rest.Singleton1@782830e
- com.qq.qq.mal.rest.Singleton1@782830e
- 反序列化创建实例:com.hbis.ttie.mal.rest.Singleton1@782830e
查看控制台输出,发现是同一对象
- // 饿汉式
- public class Singleton1 implements Serializable {
-
- // 构造私有
- private Singleton1() {
- if (INSTANCE != null) {
- throw new RuntimeException("单例对象不能重复创建");
- }
- System.out.println("private Singleton1()");
- }
-
- // 静态成员变量
- private static final Singleton1 INSTANCE = new Singleton1();
-
- // 公共静态方法
- public static Singleton1 getInstance() {
- return INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
-
- // 方法名是固定的
- // 在反序列化时,发现重写了 readResolve() ,那么会利用 readResolve() 的返回值,作为结果返回
- public Object readResolve() {
- return INSTANCE;
- }
-
- }
- public static void main(String[] args) throws Exception {
- Singleton1.otherMethod();
- System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
-
- // 调用两次,看看是否是同一实例
- System.out.println(Singleton1.getInstance());
- System.out.println(Singleton1.getInstance());
-
- // Unsafe 破坏单例
- unsafe(Singleton1.class);
- }
-
- // unsafe是jdk的内置类,不能直接访问,
- private static void unsafe(Class> clazz) throws InstantiationException {
- Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz); // 根据类型,创建一个实例,也不会调用构造方法
- System.out.println("Unsafe 创建实例:" + o);
- }
-
- 控制台输出:
- private Singleton1()
- otherMethod()
- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- com.qq.qq.mal.rest.Singleton1@782830e
- com.qq.qq.mal.rest.Singleton1@782830e
- Unsafe 创建实例:com.hbis.ttie.mal.rest.Singleton1@5fdef03a
查看控制台输出,发现创建了两个实例
还没找到预防方法
- enum Sex {
- MALE, FEMALE;
- }
光看代码不好理解,对其进行反编译,得到如下代码:
- final class Sex extends Enum<Sex> {
- public static final Sex MALE;
- public static final Sex FEMALE;
-
- private Sex(String name, int ordinal) {
- super(name, ordinal);
- }
-
- static {
- MALE = new Sex("MALE", 0);
- FEMALE = new Sex("FEMALE", 1);
- $VALUES = values();
- }
-
- private static final Sex[] $VALUES;
-
- private static Sex[] $values() {
- return new Sex[]{MALE, FEMALE};
- }
-
- public static Sex[] values() {
- return $VALUES.clone();
- }
-
- public static Sex valueOf(String value) {
- return Enum.valueOf(Sex.class, value);
- }
- }
final:修饰,说明不能被继承,不能有子类;
Enum
枚举类的饿汉式单例
- // 枚举饿汉式
- public enum Singleton2 {
- INSTANCE;
-
- // 以下代码,都是不必要的,为了测试方便而添加的
-
- // 构造方法,默认是 private 的,去掉以后,依然是私有的
- // idea也提示 Modifier 'private' is redundant for enum constructors 修饰符'private'对于enum构造函数是多余的
- // 主要是为了打印信息,看构造方法是否调用
- private Singleton2() {
- System.out.println("private Singleton2()");
- }
-
- // 打印枚举类时,把 hash 码也打印出来,能区分是否是同一对象,默认打印枚举的名字
- @Override
- public String toString() {
- return getClass().getName() + "@" + Integer.toHexString(hashCode());
- }
-
- // 静态公共方法,获取单例
- public static Singleton2 getInstance() {
- return INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
- }
- public static void main(String[] args) throws Exception {
- Singleton2.otherMethod();
- System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
-
- // 调用两次,看看是否是同一实例
- System.out.println(Singleton2.getInstance());
- System.out.println(Singleton2.getInstance());
-
- }
-
- 控制台输出:
- private Singleton2()
- otherMethod()
- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- com.qq.qq.mal.rest.Singleton2@782830e
- com.qq.qq.mal.rest.Singleton2@782830e
调用静态方法 otherMethod(),可以触发 Singleton2 的初始化操作,类初始化操作时,会调用构造方法,单例对象会被创建,getInstance() 时,拿到的是已经创建好的对象;
枚举饿汉式能天然防止反射(反射调用时会对枚举类型进行相应的检查)、反序列化(反序列化时会对枚举类进行特殊的处理)破坏单例
- public enum Singleton2 {
- INSTANCE;
-
- // 以下代码,都是不必要的,为了测试方便而添加的
-
- // 构造方法,默认是 private 的,去掉以后,依然是私有的
- // idea也提示 Modifier 'private' is redundant for enum constructors 修饰符'private'对于enum构造函数是多余的
- // 主要是为了打印信息,看构造方法是否调用
- private Singleton2() {
- System.out.println("private Singleton2()");
- }
-
- // 打印枚举类时,把 hash 码也打印出来,能区分是否是同一对象,默认打印枚举的名字
- @Override
- public String toString() {
- return getClass().getName() + "@" + Integer.toHexString(hashCode());
- }
-
- // 静态公共方法,获取单例
- public static Singleton2 getInstance() {
- return INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
- }
- public static void main(String[] args) throws Exception {
- Singleton2.otherMethod();
- System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
-
- // 调用两次,看看是否是同一实例
- System.out.println(Singleton2.getInstance());
- System.out.println(Singleton2.getInstance());
-
- // Unsafe 破坏单例
- unsafe(Singleton2.class);
- }
-
- private static void unsafe(Class> clazz) throws InstantiationException {
- Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
- System.out.println("Unsafe 创建实例:" + o);
- }
-
- 控制台输出:
- private Singleton2()
- otherMethod()
- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- com.qq.qq.mal.rest.Singleton2@782830e
- com.qq.qq.mal.rest.Singleton2@782830e
- Unsafe 创建实例:com.hbis.ttie.mal.rest.Singleton2@5fdef03a
可以看到 Unsafe 创建了另一个对象
在使用时才会创建单例对象
- // 懒汉式单例
- public class Singleton3 implements Serializable {
- private Singleton3() {
- System.out.println("private Singleton3()");
- }
-
- private static Singleton3 INSTANCE = null;
-
- public static Singleton3 getInstance() {
- if (INSTANCE == null) {
- INSTANCE = new Singleton3();
- }
- return INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
-
- }
- public static void main(String[] args) throws Exception {
- Singleton3.otherMethod();
- System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
- System.out.println(Singleton3.getInstance());
- System.out.println(Singleton3.getInstance());
-
- }
-
- 控制台输出:
- otherMethod()
- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- private Singleton3()
- com.qq.qq.mal.rest.Singleton3@782830e
- com.qq.qq.mal.rest.Singleton3@782830e
在调用 otherMethod() 时,未调用单例的构造方法,第一次调用 getInstance() 时,才会调用单例的构造方法,创建单例对象
懒汉式单例需要考虑是否运行在多线程环境下,需要考虑线程安全的问题
两个线程,同时调用 getInstance() 方法,线程1通过 if (INSTANCE == null) 校验,进入代码块,并未执行 INSTANCE = new Singleton3() 时,线程2也通过了 if (INSTANCE == null) 的校验,进入代码块,线程1、线程2都执行INSTANCE = new Singleton3(),创建了连个对象,就不再是单例对象了
如何解决?将代码改进一下
- // 懒汉式单例
- public class Singleton3 implements Serializable {
- private Singleton3() {
- System.out.println("private Singleton3()");
- }
-
- private static Singleton3 INSTANCE = null;
-
- // Singleton3.class
- public static synchronized Singleton3 getInstance() {
- if (INSTANCE == null) {
- INSTANCE = new Singleton3();
- }
- return INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
- }
getInstance() 方法加 synchronized 关键字,对方法进行线程安全的保护
加在静态方法上的 synchronized,会给当前类(Singleton3.class)的 class 对象加一把锁,想进入方法,需要先获取锁,未释放锁前,其他线程无法进入
但是把 synchronized 加在整个方法上,虽然可以解决问题,但是性能上不好
查看代码发现,首次创建单例对象时,需要线程安全保护,单例对象创建后,就不存在线程安全问题了,因此,需求是首次创建单例对象时,有线程安全保护,后续的调用,无需线程安全保护
那么如何改进呢?
使用DCL懒汉式单例即双检锁懒汉式
- // 懒汉式单例 - DCL
- public class Singleton4 implements Serializable {
- private Singleton4() {
- System.out.println("private Singleton4()");
- }
-
- private static volatile Singleton4 INSTANCE = null; // 可见性,有序性
-
- public static Singleton4 getInstance() {
- if (INSTANCE == null) {
- synchronized (Singleton4.class) {
- if (INSTANCE == null) {
- INSTANCE = new Singleton4();
- }
- }
- }
- return INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
- }
- public static void main(String[] args) throws Exception {
- Singleton4.otherMethod();
- System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
- System.out.println(Singleton4.getInstance());
- System.out.println(Singleton4.getInstance());
-
- }
-
- 控制台输出:
- otherMethod()
- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- private Singleton4()
- com.qq.qq.mal.rest.Singleton4@782830e
- com.qq.qq.mal.rest.Singleton4@782830e
代码的关键在于两次 if (INSTANCE == null) ,这也是双检索名字的来源
假如没有内部的检查
- public static Singleton4 getInstance() {
- if (INSTANCE == null) {
- synchronized (Singleton4.class) {
- INSTANCE = new Singleton4();
- }
- }
- return INSTANCE;
- }
首次创建单例对象时,线程1、线程2同时进行,因为 INSTANCE = null,所以可以通过外部 if (INSTANCE == null) 校验,进入 if 代码块,这次线程2首先拿到了锁,然后创建单例对象,然后解锁返回,这时线程1获得锁,进入代码块,因为没有内部 if (INSTANCE == null) 校验,会再次创建一个单例对象,返回解锁返回,这就有问题了;
双检锁中,需要给静态变量使用 volatile 来修饰(volatile 可以解决共享变量的可见性,有序性问题),在双检锁中使用 volatile,是为了保证有序性
为何必须加 volatile?
`INSTANCE = new Singleton4()` 不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值
CPU可能会对指令的执行次序进行优化(如果两指令之间,没有因果关系,就可能会被调换执行次序)
创建对象即分配内存空间,一定会在调用构造、给静态变量赋值前执行,调用构造方法是目的是为了给当前实例的成员变量进行初始化赋值,静态变量赋值也是一个赋值操作
其中后两步可能被指令重排序优化,变成先赋值、再调用构造,在单线程下,调换顺序没有问题,是一种优化手段,但是如果多线程环境下,就可能有问题了
如果线程1 ,执行 INSTANCE = new Singleton4() ,先执行了赋值,还未调用构造,此时线程2 执行到第一个 if (INSTANCE == null) 校验,发现 INSTANCE 已经不为 null,就会返回,但是构造方法还没有执行,返回的是一个未完整构造的对象
解决方法就是给共享变量加 volatile 修饰(大意是加了 volatile 修饰,会在赋值语句后,加一个内存屏障,也就说是不会出现调用构造方法时的赋值,越过内存屏障,出现在给静态变量赋值之后)
饿汉式单例无需考虑多线程下对象创建的问题
- // 静态成员变量
- private static final Singleton1 INSTANCE = new Singleton1();
单例对象赋值给了静态成员变量,给静态变量赋值的操作,会放在这个类的静态代码块中执行,静态代码块中的线程安全有虚拟机来负责,我们无需考虑
将对象的创建,放入静态代码块中,那就意味着是线程安全的
- // 懒汉式单例 - 内部类
- public class Singleton5 implements Serializable {
- private Singleton5() {
- System.out.println("private Singleton5()");
- }
-
- // 静态内部类
- private static class Holder {
- static Singleton5 INSTANCE = new Singleton5();
- }
-
- public static Singleton5 getInstance() {
- return Holder.INSTANCE;
- }
-
- public static void otherMethod() {
- System.out.println("otherMethod()");
- }
- }
首先需要创建一个静态内部类,内部类可以访问外部类的私有变量,私有方法, 所以在内部类中创建了单例对象,并且赋值给了内部类的静态变量,刚才说了,静态变量的赋值是放在静态代码块中执行的,那么对象的创建(new Singleton5())就是线程安全的
在 getInstance() 方法中使用内部类访问它的变量,这是就会触发内部类的加载,链接,初始化,在初始化时,会创建单例对象,这样既有懒汉式的特性又能保证创建时的线程安全
- public static void main(String[] args) throws Exception {
- Singleton5.otherMethod();
- System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
- System.out.println(Singleton5.getInstance());
- System.out.println(Singleton5.getInstance());
-
- }
-
- 控制台输出:
- otherMethod()
- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- private Singleton5()
- com.qq.qq.mal.rest.Singleton5@782830e
- com.qq.qq.mal.rest.Singleton5@782830e
* Runtime 体现了饿汉式单例
* Console 体现了双检锁懒汉式单例
* Collections 中的 EmptyNavigableSet 内部类懒汉式单例
* ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
* Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例