设计模式那么多,面试中常问的也就单例模式了

考察:单例模式的五种实现方式,jdk 中使用单例模式的场景,为何 DCL(双重检查锁) 实现时要使用 volatile 修饰静态变量

1、饿汉式

顾名思义,很饿,所以类初始化时就创建了对象(单例的,用 static 修饰)

public class Singleton1 implements Serializable {
    private static final Singleton1 INSTANCE = new Singleton1();
    private Singleton1(){
        //构造私有
    }
    public static Singleton1 getInstance() {
        return INSTANCE;
    }
}

但是上面的代码不够健壮,可以通过反射或者反序列化来破坏单例,我们进一步修改

public class Singleton1 implements Serializable {
    private static final Singleton1 INSTANCE = new Singleton1();
    private Singleton1(){
        //防止反射破坏单例
        if (INSTANCE != null){
            throw new RuntimeException("单例对象无法重复创建实例");
        }
        System.out.println("构造私有");
    }
    public static Singleton1 getInstance() {
        return INSTANCE;
    }
    //防止反序列化破坏单例,方法名固定的
    public Object readResolve(){
        return INSTANCE;
    }
    public void method(){
        System.out.println("method");
    }
    public static void staticMethod(){
        System.out.println("static method");
    }
}

测试代码

public class TestSingleton {
    public static void main(String[] args) throws Exception {
        Singleton1.staticMethod();
        Singleton1.getInstance().method();
        System.out.println(Singleton1.getInstance());
        //反射调用私有构造破坏单例
        //reflection(Singleton1.class);

        //反序列化破坏单例
        //serializable(Singleton1.getInstance());

        //Unsafe破坏单例,无法预防
        unsafe(Singleton1.class);
    }

    public static void reflection(Class<?> clazz) throws Exception {
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        System.out.println("反射创建实例," + constructor.newInstance());
    }
    public static void serializable(Object instance) throws Exception {
        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 static void unsafe(Class<?> clazz) throws Exception {
        Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
        System.out.println("Unsafe创建实例," + o);
    }
}

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

3、懒汉式

public class Singleton3 implements Serializable {
    private static Singleton3 INSTANCE = null;
    private Singleton3() {
        System.out.println("private Singleton3()");
    }
    public static synchronized Singleton3 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

其实只有首次创建单例对象时才需要同步锁,但该代码实际上每次调用都会同步锁,因此有了后面的双检锁改进

4、双检锁懒汉式

public class Singleton4 implements Serializable {
    private Singleton4() {
        System.out.println("private Singleton4()");
    }
    // 加 volatile 解决共享变量的可见性,有序性问题
    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 = new Singleton4() 过程:创建对象、调用构造、给静态变量赋值,后两步可能被指令重排序优化,变成先赋值、再调用构造,此时如果线程1 先执行了赋值,线程2 执行到第一个 INSTANCE == null 时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象

加上 volatile 可以解决这一问题

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

JDK 中单例的体现

  • Runtime 体现了饿汉式单例
  • Console 体现了双检锁懒汉式单例
  • Collections 中的 EmptyNavigableSet 内部类懒汉式单例
  • ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
  • Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例

Last modification:January 27, 2022
如果觉得我的文章对你有用,请随意赞赏