面经-单例模式

单例模式的五种实现方式

1.饿汉式

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 Object readResolve() {
        return INSTANCE;
    }
}

 

类一初始化,实例就会提前创建出来。

破坏单例的场景:

1.反射破坏单例

通过调用无参构造方法,将私有的构造方法设为true,使它也可以被使用,然后再创建它的实例。导致它不再是单例。

预防:

在构造方法里加一个判断:如果instance不等于null,则抛出一个异常:单例对象不能重复创建。

 

2.反序列化破坏单例

把单例对象传给序列化方法,把对象变成字节流,再把字节流还原成一个对象,在反序列化时会构造一个新的对象。

预防:

写一个特殊的方法:readResolve()。里边返回instance。如果发现重写了这个方法,则将这个方法的返回值作为结果返回。

 

3.Unsafe破坏单例

调用unsafe方法破坏单例,目前没有找到预防方法。

 

2.枚举类(饿汉式)

public enum Singleton2 {
    INSTANCE;

    private Singleton2() {
        System.out.println("private Singleton2()");
    }

    @Override
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    public static Singleton2 getInstance() {
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

 

可以很方便地控制对象的个数。

好处:

1.不怕反射(没有无参构造,不能反射创建枚举构造)、反序列化破坏单例。

2.Unsafe可以破坏枚举单例。

 

3.懒汉式

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时才会生成实例。

一开始的Instance变量不赋值,在调用时判断Instance是否为null,如果是则创建。不是则直接返回Instance。

线程安全问题:

线程不安全,线程1创建对象后还没来得及赋值,线程2就判断Instance为null,再次创建对象。

解决方法:

在方法上加一个synchronized,相当于在方法上加了一把锁,线程1进入方法后得到一把锁,线程2进入方法时发现锁对象被别人占用,则不执行,挂起。当线程1退出这个方法时会把锁解开,线程2继续执行时线程1的赋值已经完成,所以线程2可以直接使用线程1创建好的instance。(对性能有一定影响。只有首次创建对象时才需要锁,但是创建好对象后只能进一个线程,影响性能。)

 

首次创建对象时加入线程安全保护:

4.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()");
    }
}

 

在加锁之前先判断instance是否已创建好,如果已创建则直接返回instance,不会进入锁块。如果没有创建才进入锁,解决instance的竞争问题。当线程2拿到锁时,线程1挂起,线程2创建好对象并解锁,线程1经过第二个if判断发现已创建好对象,拿到直接返回的instance。

双检索的方法外面必须加volatile修饰。

解决共享变量的可见性和有序性问题。双检索变量是为了解决有序性问题。

线程1调用对象和给对象赋值的顺序有可能颠倒,可能先给对象赋值再调用对象(里边可能还有很多成员变量没有初始化);此时线程2在线程1给对象赋值后,就判断instance不等于null,然后返回了instance,此时线程1才调用对象。此时线程2拿到的就是不完整的对象。

加上volatile修饰后,volatile会在赋值语句之后加上一个内存屏障,阻止之前的赋值操作越过屏障跑到屏障下边,阻止指令的重排序。构造方法不可能越过屏障跑到赋值下边。

这样的话,即使线程2在线程1完成前进入方法,也无法在赋值完成之前拿到instance,会在锁中等待解锁后才拿到返回值。

 

5.内部类懒汉式

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()");
    }
}

 

给静态变量赋值,代码一定会放入静态代码块执行。由静态代码块执行的代码,线程安全问题由jvm保证。

在类的内部创建一个内部类,内部类可以访问外部的私有变量和私有构造。在内部类里创建单例对象,赋值给内部静态变量。创建过程是线程安全的,而且没有用到这个内部类的时候不会初始化instance,所以是懒汉式。

 

 

jdk中有哪些地方体现了单例模式

  • Runtime() 体现了饿汉式单例

  • System中的Console方法体现了双检锁懒汉式单例

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

  • ReverseComparator.REVERSE_ORDER 内部类懒汉式单例

  • Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例

 
posted @ 2022-08-24 10:42  临易  阅读(26)  评论(0编辑  收藏  举报