一、什么是单例模式?

一个类只有一个实例,多个线程同时访问,获取到的也还是同一个对象实例。主要用于解决:一个全局使用的类,被频繁地创建与销毁,从而提高代码的整体性能。

二、如何实现一个单例?

  1. 构造器私有化
  2. 暴露一个公共的获取单例对象的接口

三、单例模式的实现

3.1 饿汉式(线程安全)

public class EagerSingleton {
	private static EagerSingleton instance = new EagerSingleton();
    private EagerSingleton() {}
    
    public static EagerSingleton getInstance() {
        return instance;
    }
}

3.2 懒汉式(线程不安全)

public class LazySingleton {
	private static LazySingleton instance;
    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
        	instance = new LazySingleton();
        }
        return instance;
    }
}

此种方式线程不安全,假设第一次进入,还没有实例对象,当多个线程同时进入到if判断时,可能都会进入到判断中,执行new LazySingleton();的操作,会导致JVM中存在多个该类的实例对象。

3.3 懒汉式(线程安全)

public class LazySingleton {
	private static LazySingleton instance;
    private LazySingleton() {}

    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
        	instance = new LazySingleton();
        }
        return instance;
    }
}

这种方式虽然是线程安全,但是因为把锁加到方法中后,所有的访问因为需要锁占用,会导致资源浪费。

3.4 双重检查锁(线程安全)

public class DclSingleton {
    private static volatile DclSingleton instance;
	private DclSingleton() {}

    public static DclSingleton getInstance() {
        if (instance == null) {
        	synchronized(DclSingleton.class) {
                if (instance == null) {
                    instance = new DclSingleton();
                }
            }
        }
        return instance;
    }
}

注意关键字volatile,如果不加可能会出现半初始化的对象。

3.5 静态内部类(线程安全)

public class InnerSingleton {
    private static class SingletonHolder {
        private static final InnerSingleton INSTANCE = new InnerSingleton();
    }

    private InnerSingleton(){}

    public static InnerSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

3.6 枚举(线程安全)

public enum EnumSingleton {
    INSTANCE;

    public void test() {
        System.out.println("Hello World!");
    }
}

如果存在继承关系,此种方式不可用。可解决序列化和反序列化、反射入侵等安全问题。

3.7 解决反射入侵

public class Singleton {
    private volatile static Singleton singleton;
    private Singleton (){
        if(singleton != null) 
            throw new RuntimeException("实例:【"
                    + this.getClass().getName() + "】已经存在,该实例只允许实例化一次");
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

如果存在实例对象了,还去创建,可以直接抛出一个异常来解决。

3.8 解决序列化和反序列化的安全问题

public class Singleton implements Serializable {
    private volatile static Singleton singleton;
    private Singleton (){
        if(singleton != null) 
            throw new RuntimeException("实例:【"
                    + this.getClass().getName() + "】已经存在,该实例只允许实例化一次");
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    public Object readResolve() {
        return singleton;
    }
}

原因:序列化后,在反序列化的时候,会将反序列化的数据克隆成一个新的实例对象。

解决方案:重写Serializable的readResolve()方法,将单例的实例对象返回即可。

天行健,君子以自强不息