• 面试:单例模式


    1.饿汉式

    1.1介绍

    饿汉式是相对于懒汉式来说的,懒汉式是第一次调用 getInstance() 方法时,才创建实例,而饿汉式则是不调用 getInstance() 方法,类初始化时,实例会被提前创建出来

    1. // 1. 饿汉式
    2. public class Singleton1 implements Serializable {
    3. // 构造私有
    4. private Singleton1() {
    5. System.out.println("private Singleton1()");
    6. }
    7. // 静态成员变量
    8. private static final Singleton1 INSTANCE = new Singleton1();
    9. // 公共静态方法
    10. public static Singleton1 getInstance() {
    11. return INSTANCE;
    12. }
    13. public static void otherMethod() {
    14. System.out.println("otherMethod()");
    15. }
    16. }
    1. public class TestSingleton {
    2. public static void main(String[] args) throws Exception {
    3. Singleton1.otherMethod();
    4. System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    5. // 调用两次,看看是否是同一实例
    6. System.out.println(Singleton1.getInstance());
    7. System.out.println(Singleton1.getInstance());
    8. }
    9. }
    10. 控制台输出:
    11. private Singleton1()
    12. otherMethod()
    13. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    14. com.ee.dd.mal.rest.Singleton1@782830e
    15. com.ee.dd.mal.rest.Singleton1@782830e

    查看控制台输出:

    com.ee.dd.mal.rest.Singleton1@782830e
    com.ee.dd.mal.rest.Singleton1@782830e

    说明是同一对象;

    调用静态方法 otherMethod(),可以触发 Singleton1 的初始化操作,类初始化操作时,会调用构造方法,单例对象会被创建,getInstance() 时,拿到的是已经创建好的对象;

    1.2单例的破坏方法

    1.2.1反射破坏单例

    1. // 饿汉式
    2. public class Singleton1 implements Serializable {
    3. // 构造私有
    4. private Singleton1() {
    5. System.out.println("private Singleton1()");
    6. }
    7. // 静态成员变量
    8. private static final Singleton1 INSTANCE = new Singleton1();
    9. // 公共静态方法
    10. public static Singleton1 getInstance() {
    11. return INSTANCE;
    12. }
    13. public static void otherMethod() {
    14. System.out.println("otherMethod()");
    15. }
    16. }
    1. public static void main(String[] args) throws Exception {
    2. Singleton1.otherMethod();
    3. System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    4. // 调用两次,看看是否是同一实例
    5. System.out.println(Singleton1.getInstance());
    6. System.out.println(Singleton1.getInstance());
    7. // 反射破坏单例
    8. reflection(Singleton1.class);
    9. }
    10. private static void reflection(Class clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
    11. Constructor constructor = clazz.getDeclaredConstructor(); // 拿到一个无参的构造方法
    12. constructor.setAccessible(true); // 设置私有的构造方法,也可以被使用
    13. System.out.println("反射创建实例:" + constructor.newInstance()); // 调用构造方法的 newInstance() 也可以创建实例
    14. }
    15. 控制台输出:
    16. private Singleton1()
    17. otherMethod()
    18. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    19. com.qq.ww.mal.rest.Singleton1@782830e
    20. com.qq.ww.mal.rest.Singleton1@782830e
    21. private Singleton1()
    22. 反射创建实例:com.hbis.ttie.mal.rest.Singleton1@470e2030

    可以看到,getInstance() 拿到一个对象,通过反射,构造方法的信息输出了出来,创建了另一个对象,一个类,两个对象,就不是单例了

    如何预防?

    修改代码

    1. // 饿汉式
    2. public class Singleton1 implements Serializable {
    3. // 构造私有
    4. private Singleton1() {
    5. if (INSTANCE != null) {
    6. throw new RuntimeException("单例对象不能重复创建");
    7. }
    8. System.out.println("private Singleton1()");
    9. }
    10. // 静态成员变量
    11. private static final Singleton1 INSTANCE = new Singleton1();
    12. // 公共静态方法
    13. public static Singleton1 getInstance() {
    14. return INSTANCE;
    15. }
    16. public static void otherMethod() {
    17. System.out.println("otherMethod()");
    18. }
    19. }
    1. public static void main(String[] args) throws Exception {
    2. Singleton1.otherMethod();
    3. System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    4. // 调用两次,看看是否是同一实例
    5. System.out.println(Singleton1.getInstance());
    6. System.out.println(Singleton1.getInstance());
    7. // 反射破坏单例
    8. reflection(Singleton1.class);
    9. }
    10. private static void reflection(Class clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
    11. Constructor constructor = clazz.getDeclaredConstructor(); // 拿到一个无参的构造方法
    12. constructor.setAccessible(true); // 设置私有的构造方法,也可以被使用
    13. System.out.println("反射创建实例:" + constructor.newInstance()); // 调用构造方法的 newInstance() 也可以创建实例
    14. }
    15. 控制台输出:
    16. private Singleton1()
    17. otherMethod()
    18. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    19. com.qq.qq.mal.rest.Singleton1@782830e
    20. com.qq.qq.mal.rest.Singleton1@782830e
    21. Exception in thread "main" java.lang.reflect.InvocationTargetException
    22. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    23. at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    24. at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    25. at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    26. at com.qq.qq.mal.rest.TestSingleton.reflection(TestSingleton.java:25)
    27. at com.qq.qq.mal.rest.TestSingleton.main(TestSingleton.java:19)
    28. Caused by: java.lang.RuntimeException: 单例对象不能重复创建
    29. at com.qq.qq.mal.rest.Singleton1.<init>(Singleton1.java:11)
    30. ... 6 more

    发现反射调用的时候,报错了

    1.2.2反序列化破坏单例

    如果单例对象实现了 Serializable 接口,单例可以被破坏

    1. // 饿汉式
    2. public class Singleton1 implements Serializable {
    3. // 构造私有
    4. private Singleton1() {
    5. if (INSTANCE != null) {
    6. throw new RuntimeException("单例对象不能重复创建");
    7. }
    8. System.out.println("private Singleton1()");
    9. }
    10. // 静态成员变量
    11. private static final Singleton1 INSTANCE = new Singleton1();
    12. // 公共静态方法
    13. public static Singleton1 getInstance() {
    14. return INSTANCE;
    15. }
    16. public static void otherMethod() {
    17. System.out.println("otherMethod()");
    18. }
    19. }
    1. public static void main(String[] args) throws Exception {
    2. Singleton1.otherMethod();
    3. System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    4. // 调用两次,看看是否是同一实例
    5. System.out.println(Singleton1.getInstance());
    6. System.out.println(Singleton1.getInstance());
    7. // 反序列化破坏单例
    8. serializable(Singleton1.getInstance());
    9. }
    10. private static void serializable(Object instance) throws IOException, ClassNotFoundException {
    11. ByteArrayOutputStream bos = new ByteArrayOutputStream();
    12. ObjectOutputStream oos = new ObjectOutputStream(bos);
    13. oos.writeObject(instance); // 变成字节流
    14. ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
    15. System.out.println("反序列化创建实例:" + ois.readObject()); // 把字节流还原成对象,这是一个新的对象,并且不调用构造方法
    16. }
    17. 控制台输出:
    18. private Singleton1()
    19. otherMethod()
    20. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    21. com.qq.qq.mal.rest.Singleton1@782830e
    22. com.qq.qq.mal.rest.Singleton1@782830e
    23. 反序列化创建实例:com.hbis.ttie.mal.rest.Singleton1@2280cdac

    查看控制台输出,创建了两个对象

    如何预防?

    1. // 饿汉式
    2. public class Singleton1 implements Serializable {
    3. // 构造私有
    4. private Singleton1() {
    5. if (INSTANCE != null) {
    6. throw new RuntimeException("单例对象不能重复创建");
    7. }
    8. System.out.println("private Singleton1()");
    9. }
    10. // 静态成员变量
    11. private static final Singleton1 INSTANCE = new Singleton1();
    12. // 公共静态方法
    13. public static Singleton1 getInstance() {
    14. return INSTANCE;
    15. }
    16. public static void otherMethod() {
    17. System.out.println("otherMethod()");
    18. }
    19. // 方法名是固定的
    20. // 在反序列化时,发现重写了 readResolve() ,那么会利用 readResolve() 的返回值,作为结果返回
    21. public Object readResolve() {
    22. return INSTANCE;
    23. }
    24. }
    1. public static void main(String[] args) throws Exception {
    2. Singleton1.otherMethod();
    3. System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    4. // 调用两次,看看是否是同一实例
    5. System.out.println(Singleton1.getInstance());
    6. System.out.println(Singleton1.getInstance());
    7. // 反序列化破坏单例
    8. serializable(Singleton1.getInstance());
    9. }
    10. private static void serializable(Object instance) throws IOException, ClassNotFoundException {
    11. ByteArrayOutputStream bos = new ByteArrayOutputStream();
    12. ObjectOutputStream oos = new ObjectOutputStream(bos);
    13. oos.writeObject(instance); // 变成字节流
    14. ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
    15. System.out.println("反序列化创建实例:" + ois.readObject()); // 把字节流还原成对象,这是一个新的对象,并且不调用构造方法
    16. }
    17. 控制台输出:
    18. private Singleton1()
    19. otherMethod()
    20. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    21. com.qq.qq.mal.rest.Singleton1@782830e
    22. com.qq.qq.mal.rest.Singleton1@782830e
    23. 反序列化创建实例:com.hbis.ttie.mal.rest.Singleton1@782830e

    查看控制台输出,发现是同一对象

    1.2.3Unsafe 破坏单例

    1. // 饿汉式
    2. public class Singleton1 implements Serializable {
    3. // 构造私有
    4. private Singleton1() {
    5. if (INSTANCE != null) {
    6. throw new RuntimeException("单例对象不能重复创建");
    7. }
    8. System.out.println("private Singleton1()");
    9. }
    10. // 静态成员变量
    11. private static final Singleton1 INSTANCE = new Singleton1();
    12. // 公共静态方法
    13. public static Singleton1 getInstance() {
    14. return INSTANCE;
    15. }
    16. public static void otherMethod() {
    17. System.out.println("otherMethod()");
    18. }
    19. // 方法名是固定的
    20. // 在反序列化时,发现重写了 readResolve() ,那么会利用 readResolve() 的返回值,作为结果返回
    21. public Object readResolve() {
    22. return INSTANCE;
    23. }
    24. }
    1. public static void main(String[] args) throws Exception {
    2. Singleton1.otherMethod();
    3. System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    4. // 调用两次,看看是否是同一实例
    5. System.out.println(Singleton1.getInstance());
    6. System.out.println(Singleton1.getInstance());
    7. // Unsafe 破坏单例
    8. unsafe(Singleton1.class);
    9. }
    10. // unsafe是jdk的内置类,不能直接访问,
    11. private static void unsafe(Class clazz) throws InstantiationException {
    12. Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz); // 根据类型,创建一个实例,也不会调用构造方法
    13. System.out.println("Unsafe 创建实例:" + o);
    14. }
    15. 控制台输出:
    16. private Singleton1()
    17. otherMethod()
    18. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    19. com.qq.qq.mal.rest.Singleton1@782830e
    20. com.qq.qq.mal.rest.Singleton1@782830e
    21. Unsafe 创建实例:com.hbis.ttie.mal.rest.Singleton1@5fdef03a

    查看控制台输出,发现创建了两个实例

    还没找到预防方法

    2.枚举饿汉式

    2.1介绍

    1. enum Sex {
    2. MALE, FEMALE;
    3. }

    光看代码不好理解,对其进行反编译,得到如下代码:

    1. final class Sex extends Enum<Sex> {
    2. public static final Sex MALE;
    3. public static final Sex FEMALE;
    4. private Sex(String name, int ordinal) {
    5. super(name, ordinal);
    6. }
    7. static {
    8. MALE = new Sex("MALE", 0);
    9. FEMALE = new Sex("FEMALE", 1);
    10. $VALUES = values();
    11. }
    12. private static final Sex[] $VALUES;
    13. private static Sex[] $values() {
    14. return new Sex[]{MALE, FEMALE};
    15. }
    16. public static Sex[] values() {
    17. return $VALUES.clone();
    18. }
    19. public static Sex valueOf(String value) {
    20. return Enum.valueOf(Sex.class, value);
    21. }
    22. }

    final:修饰,说明不能被继承,不能有子类;

    Enum:枚举父类,不能在代码中直接写,是编译不通过的,继承关系是编译器在编译时加上的;

    枚举类的饿汉式单例

    1. // 枚举饿汉式
    2. public enum Singleton2 {
    3. INSTANCE;
    4. // 以下代码,都是不必要的,为了测试方便而添加的
    5. // 构造方法,默认是 private 的,去掉以后,依然是私有的
    6. // idea也提示 Modifier 'private' is redundant for enum constructors 修饰符'private'对于enum构造函数是多余的
    7. // 主要是为了打印信息,看构造方法是否调用
    8. private Singleton2() {
    9. System.out.println("private Singleton2()");
    10. }
    11. // 打印枚举类时,把 hash 码也打印出来,能区分是否是同一对象,默认打印枚举的名字
    12. @Override
    13. public String toString() {
    14. return getClass().getName() + "@" + Integer.toHexString(hashCode());
    15. }
    16. // 静态公共方法,获取单例
    17. public static Singleton2 getInstance() {
    18. return INSTANCE;
    19. }
    20. public static void otherMethod() {
    21. System.out.println("otherMethod()");
    22. }
    23. }
    1. public static void main(String[] args) throws Exception {
    2. Singleton2.otherMethod();
    3. System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    4. // 调用两次,看看是否是同一实例
    5. System.out.println(Singleton2.getInstance());
    6. System.out.println(Singleton2.getInstance());
    7. }
    8. 控制台输出:
    9. private Singleton2()
    10. otherMethod()
    11. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    12. com.qq.qq.mal.rest.Singleton2@782830e
    13. com.qq.qq.mal.rest.Singleton2@782830e

    调用静态方法 otherMethod(),可以触发 Singleton2 的初始化操作,类初始化操作时,会调用构造方法,单例对象会被创建,getInstance() 时,拿到的是已经创建好的对象;

    2.2破坏方法

    枚举饿汉式能天然防止反射(反射调用时会对枚举类型进行相应的检查)、反序列化(反序列化时会对枚举类进行特殊的处理)破坏单例

    2.2.1Unsafe 破坏单例

    1. public enum Singleton2 {
    2. INSTANCE;
    3. // 以下代码,都是不必要的,为了测试方便而添加的
    4. // 构造方法,默认是 private 的,去掉以后,依然是私有的
    5. // idea也提示 Modifier 'private' is redundant for enum constructors 修饰符'private'对于enum构造函数是多余的
    6. // 主要是为了打印信息,看构造方法是否调用
    7. private Singleton2() {
    8. System.out.println("private Singleton2()");
    9. }
    10. // 打印枚举类时,把 hash 码也打印出来,能区分是否是同一对象,默认打印枚举的名字
    11. @Override
    12. public String toString() {
    13. return getClass().getName() + "@" + Integer.toHexString(hashCode());
    14. }
    15. // 静态公共方法,获取单例
    16. public static Singleton2 getInstance() {
    17. return INSTANCE;
    18. }
    19. public static void otherMethod() {
    20. System.out.println("otherMethod()");
    21. }
    22. }
    1. public static void main(String[] args) throws Exception {
    2. Singleton2.otherMethod();
    3. System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    4. // 调用两次,看看是否是同一实例
    5. System.out.println(Singleton2.getInstance());
    6. System.out.println(Singleton2.getInstance());
    7. // Unsafe 破坏单例
    8. unsafe(Singleton2.class);
    9. }
    10. private static void unsafe(Class clazz) throws InstantiationException {
    11. Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
    12. System.out.println("Unsafe 创建实例:" + o);
    13. }
    14. 控制台输出:
    15. private Singleton2()
    16. otherMethod()
    17. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    18. com.qq.qq.mal.rest.Singleton2@782830e
    19. com.qq.qq.mal.rest.Singleton2@782830e
    20. Unsafe 创建实例:com.hbis.ttie.mal.rest.Singleton2@5fdef03a

    可以看到 Unsafe 创建了另一个对象

    3.懒汉式单例

    3.1介绍

    在使用时才会创建单例对象

    1. // 懒汉式单例
    2. public class Singleton3 implements Serializable {
    3. private Singleton3() {
    4. System.out.println("private Singleton3()");
    5. }
    6. private static Singleton3 INSTANCE = null;
    7. public static Singleton3 getInstance() {
    8. if (INSTANCE == null) {
    9. INSTANCE = new Singleton3();
    10. }
    11. return INSTANCE;
    12. }
    13. public static void otherMethod() {
    14. System.out.println("otherMethod()");
    15. }
    16. }
    1. public static void main(String[] args) throws Exception {
    2. Singleton3.otherMethod();
    3. System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    4. System.out.println(Singleton3.getInstance());
    5. System.out.println(Singleton3.getInstance());
    6. }
    7. 控制台输出:
    8. otherMethod()
    9. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    10. private Singleton3()
    11. com.qq.qq.mal.rest.Singleton3@782830e
    12. com.qq.qq.mal.rest.Singleton3@782830e

    在调用 otherMethod() 时,未调用单例的构造方法,第一次调用 getInstance() 时,才会调用单例的构造方法,创建单例对象

    3.2多线程环境运行

    懒汉式单例需要考虑是否运行在多线程环境下,需要考虑线程安全的问题

    两个线程,同时调用 getInstance() 方法,线程1通过 if (INSTANCE == null) 校验,进入代码块,并未执行 INSTANCE = new Singleton3() 时,线程2也通过了 if (INSTANCE == null) 的校验,进入代码块,线程1、线程2都执行INSTANCE = new Singleton3(),创建了连个对象,就不再是单例对象了

    如何解决?将代码改进一下

    1. // 懒汉式单例
    2. public class Singleton3 implements Serializable {
    3. private Singleton3() {
    4. System.out.println("private Singleton3()");
    5. }
    6. private static Singleton3 INSTANCE = null;
    7. // Singleton3.class
    8. public static synchronized Singleton3 getInstance() {
    9. if (INSTANCE == null) {
    10. INSTANCE = new Singleton3();
    11. }
    12. return INSTANCE;
    13. }
    14. public static void otherMethod() {
    15. System.out.println("otherMethod()");
    16. }
    17. }

    getInstance() 方法加 synchronized 关键字,对方法进行线程安全的保护

    加在静态方法上的 synchronized,会给当前类(Singleton3.class)的 class 对象加一把锁,想进入方法,需要先获取锁,未释放锁前,其他线程无法进入

    但是把 synchronized 加在整个方法上,虽然可以解决问题,但是性能上不好

    查看代码发现,首次创建单例对象时,需要线程安全保护,单例对象创建后,就不存在线程安全问题了,因此,需求是首次创建单例对象时,有线程安全保护,后续的调用,无需线程安全保护

    那么如何改进呢?

    使用DCL懒汉式单例即双检锁懒汉式

    4.DCL懒汉式单例

    4.1介绍

    1. // 懒汉式单例 - DCL
    2. public class Singleton4 implements Serializable {
    3. private Singleton4() {
    4. System.out.println("private Singleton4()");
    5. }
    6. private static volatile Singleton4 INSTANCE = null; // 可见性,有序性
    7. public static Singleton4 getInstance() {
    8. if (INSTANCE == null) {
    9. synchronized (Singleton4.class) {
    10. if (INSTANCE == null) {
    11. INSTANCE = new Singleton4();
    12. }
    13. }
    14. }
    15. return INSTANCE;
    16. }
    17. public static void otherMethod() {
    18. System.out.println("otherMethod()");
    19. }
    20. }
    1. public static void main(String[] args) throws Exception {
    2. Singleton4.otherMethod();
    3. System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    4. System.out.println(Singleton4.getInstance());
    5. System.out.println(Singleton4.getInstance());
    6. }
    7. 控制台输出:
    8. otherMethod()
    9. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    10. private Singleton4()
    11. com.qq.qq.mal.rest.Singleton4@782830e
    12. com.qq.qq.mal.rest.Singleton4@782830e

    代码的关键在于两次 if (INSTANCE == null) ,这也是双检索名字的来源

    4.2为什么检查两次?

    假如没有内部的检查

    1. public static Singleton4 getInstance() {
    2. if (INSTANCE == null) {
    3. synchronized (Singleton4.class) {
    4. INSTANCE = new Singleton4();
    5. }
    6. }
    7. return INSTANCE;
    8. }

    首次创建单例对象时,线程1、线程2同时进行,因为 INSTANCE = null,所以可以通过外部          if (INSTANCE == null) 校验,进入 if 代码块,这次线程2首先拿到了锁,然后创建单例对象,然后解锁返回,这时线程1获得锁,进入代码块,因为没有内部 if (INSTANCE == null) 校验,会再次创建一个单例对象,返回解锁返回,这就有问题了;

    4.3为什么必须使用 volatile ?

    双检锁中,需要给静态变量使用 volatile 来修饰(volatile 可以解决共享变量的可见性,有序性问题),在双检锁中使用 volatile,是为了保证有序性

    为何必须加 volatile?

    `INSTANCE = new Singleton4()` 不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值

    CPU可能会对指令的执行次序进行优化(如果两指令之间,没有因果关系,就可能会被调换执行次序)

    创建对象即分配内存空间,一定会在调用构造、给静态变量赋值前执行,调用构造方法是目的是为了给当前实例的成员变量进行初始化赋值,静态变量赋值也是一个赋值操作

    其中后两步可能被指令重排序优化,变成先赋值、再调用构造,在单线程下,调换顺序没有问题,是一种优化手段,但是如果多线程环境下,就可能有问题了

    如果线程1 ,执行 INSTANCE = new Singleton4() ,先执行了赋值,还未调用构造,此时线程2 执行到第一个 if (INSTANCE == null) 校验,发现 INSTANCE 已经不为 null,就会返回,但是构造方法还没有执行,返回的是一个未完整构造的对象

    解决方法就是给共享变量加 volatile 修饰(大意是加了 volatile 修饰,会在赋值语句后,加一个内存屏障,也就说是不会出现调用构造方法时的赋值,越过内存屏障,出现在给静态变量赋值之后)

    4.4为什么饿汉式单例使用 volatile ?

    饿汉式单例无需考虑多线程下对象创建的问题

    1. // 静态成员变量
    2. private static final Singleton1 INSTANCE = new Singleton1();

    单例对象赋值给了静态成员变量,给静态变量赋值的操作,会放在这个类的静态代码块中执行,静态代码块中的线程安全有虚拟机来负责,我们无需考虑

    5.懒汉式单例 - 内部类

    5.1介绍

    将对象的创建,放入静态代码块中,那就意味着是线程安全的

    1. // 懒汉式单例 - 内部类
    2. public class Singleton5 implements Serializable {
    3. private Singleton5() {
    4. System.out.println("private Singleton5()");
    5. }
    6. // 静态内部类
    7. private static class Holder {
    8. static Singleton5 INSTANCE = new Singleton5();
    9. }
    10. public static Singleton5 getInstance() {
    11. return Holder.INSTANCE;
    12. }
    13. public static void otherMethod() {
    14. System.out.println("otherMethod()");
    15. }
    16. }

    首先需要创建一个静态内部类,内部类可以访问外部类的私有变量,私有方法, 所以在内部类中创建了单例对象,并且赋值给了内部类的静态变量,刚才说了,静态变量的赋值是放在静态代码块中执行的,那么对象的创建(new Singleton5())就是线程安全的

    在 getInstance() 方法中使用内部类访问它的变量,这是就会触发内部类的加载,链接,初始化,在初始化时,会创建单例对象,这样既有懒汉式的特性又能保证创建时的线程安全

    1. public static void main(String[] args) throws Exception {
    2. Singleton5.otherMethod();
    3. System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    4. System.out.println(Singleton5.getInstance());
    5. System.out.println(Singleton5.getInstance());
    6. }
    7. 控制台输出:
    8. otherMethod()
    9. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    10. private Singleton5()
    11. com.qq.qq.mal.rest.Singleton5@782830e
    12. com.qq.qq.mal.rest.Singleton5@782830e

    6.JDK 中哪些地方提现了单例模式?

    * Runtime 体现了饿汉式单例


    * Console 体现了双检锁懒汉式单例


    * Collections 中的 EmptyNavigableSet 内部类懒汉式单例


    * ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
    * Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例

     

     

  • 相关阅读:
    Sunwing.ca requests下单 请求参数介绍
    Qt图像处理技术十:得到QImage图像的高斯模糊
    Python 算法高级篇:贪心算法的原理与应用
    Java数据结构—链表与LinkedList
    【MySQL】锁
    IP证书怎么申请,如何实现加密保护
    C#实现在企业微信内创建微信群发送群消息
    什么是代理IP池?真实测评IP代理商的IP池是否真实?
    虚拟内存技术的基本概念(局部性原理,特征,实现)
    [暑假]Js对象部分的学习
  • 原文地址:https://blog.csdn.net/hfaflanf/article/details/126240165