单例模式是保证系统实例唯一性的重要手段。单例模式首先通过将类的实例化方法私有化来防止程序通过其他方式创建该类的实例,然后通过提供一个全局唯一获取该类实例的方法帮助用户获取类的实例,用户只需也只能通过调用该方法获取类的实例。下面列举常用单利模式的写法:
饿汉式单利模式
/**
* 恶汉式单利模式
*
* @Author: ganbo
* @Date: 2020/6/8 20:42
*/
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
}
为了防止在其他地方创建该类的对象讲构造方法设置private.同时提供一个static的静态方法供外部调用获取该类对象.这里是属于恶汉型.在加载该类的时候就初始化,缺点就是可能在项目运行整个过程中都不会使用该对象,这样就涉及到了内存的浪费,这就要求单利对象的初始化要满足“延迟加载”。
懒汉式单例模式
/**
* 懒汉式单利模式
*
* @Author: ganbo
* @Date: 2020/6/8 20:44
*/
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
实现了在使用的时候在初始化,但是这里通过synchronized关键字“加锁”实现避免重复初始化,这样做虽然实现了延迟加载并且避免了重复初始化,但是每次获取该对象都要排队获得锁,在并发较高的场景下效率很低不可取。
双重检查锁式单利
/**
* 双重检查锁模式单利模式
*
* @Author: ganbo
* @Date: 2020/6/8 20:57
*/
public class DoubleCheckSingleton {
/**
* 这里用volatile修饰的目的就是禁止指令重排序
*/
private volatile static DoubleCheckSingleton instance;
private DoubleCheckSingleton() {
}
public static DoubleCheckSingleton getInstance() {
//检查是否需要阻塞
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
双锁模式指在懒汉模式的基础上做进一步优化,给静态对象的定义加上volatile锁来保障初始化时对象的唯一性(这里主要是使用了volatile有禁止重排序的功能特性),在获取对象时通过synchronized (Singleton.class)给单例类加锁来保障操作的唯一性。该种方式也有一个弊端就是加锁了,在并发极高的情况下第一次获取对象的时候会排队获取锁。
静态内部类式单利(推荐)
/**
* 静态内部类方式单利模式
*
* @Author: ganbo
* @Date: 2020/6/8 20:55
*/
public class StaticInnerClassSingleton {
private static class SingletonHolder {
private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}
private StaticInnerClassSingleton() {
//防止通过反射破解该单利模式
if(SingletonHolder.instance!=null){
throw new RuntimeException("不允许重复实例化");
}
}
public StaticInnerClassSingleton getInstance() {
return SingletonHolder.instance;
}
}
静态内部类通过在类中定义一个静态内部类,将对象实例的定义和初始化放在内部类中完成,我们在获取对象时要通过静态内部类调用其单例对象。之所以这样设计,是因为类的静态内部类在JVM中是唯一的,这很好地保障了单例对象的唯一性,同时静态内部类实在使用到的时候才会加载,这样也实现了“延迟加载”的效果。这里在构造方法中加了判断的目的就是为了用反射暴力破解该单利模式,以上所有单利模式按理来说都要在构造方法中这样判断处理,防止被反射暴力破解。推荐使用。
枚举式单利
/**
* 枚举式单利
* Created by gan on 2020/6/8 22:01.
*/
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
枚举式单利也是比较推荐的单利模式,枚举式单利模式天然的做了处理,不可能通过反射的方式暴力破解。枚举式单利在类定义完了以后该类的对象就被列举出来了。推荐使用。
容器式单利
/**
* 容器式单利模式
* Created by gan on 2020/6/8 22:40.
*/
public class ContainerSingleton {
private static Map<String, Object> ioc = new ConcurrentHashMap();
private ContainerSingleton() {
}
public static Object getInstance(String beanName) {
Object instance = ioc.get(beanName);
if (instance != null) {
return ioc.get(beanName);
} else {
synchronized (ContainerSingleton.class) {
if (getInstance(beanName) == null) {
try {
instance = Class.forName(beanName).newInstance();
ioc.put(beanName, instance);
} catch (Exception e) {
e.printStackTrace();
}
}
return instance;
}
}
}
}
该方式是模拟了Spring IOC容器的实现。通过阅读Spring IOC源码可以查看到该种方式。
注意:上面的单利模式都可以通过序列化的方式暴力破解,决绝序列化暴力破解的方法就是在单利类中新增一个readResolve()方法,并且返回已经定义好的实例对象,可以通过将对象序列化和反序列化的方式验证。具体的单利写法如下,这里用饿汉式举例:
/**
* 恶汉式单利模式
*
* @Author: ganbo
* @Date: 2020/6/8 20:42
*/
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
//新增readResolve方法,方式通过序列化和反序列的方式暴力破解
private Object readResolve(){
return instance;
}
}