目录
一、单例模式的实现方式
概念:保证一个类仅有一个实例,并提供一个访问它的全局访问点,比如:线程池、缓存、对话框、注册表、日志记录等。
1、饿汉式
饿汉式是在jvm加载这个单例类的时候,就会初始化这个类中的实例,在使用单例中的实例时直接拿来使用就好,因为加载这个类的时候就已经完成初始化,并且由于是已经加载好的单例实例因此是线程安全的,并发获取的情况下不会有问题,是一种可投入使用的可靠单例。
// 测试
@Test
void test1(){
SingleInstance1.getInstance();
}
优点:使用效率高,线程安全。
缺点:由于JVM在加载单例类时需要初始化单例类的实例,对JVM内存不够友好!不推荐使用。
2、懒汉式
这种简单的懒汉式,弥补了饿汉式的缺点,JVM加载单例类时不会去初始化实例了,使用到的时候再去初始化,但在多线程环境中可能会引发线程不安全的问题,最直接的办法是在该方法上添加synchronized关键字,但这样做会锁住整个方法,也是不合理的。
优点:对JVM内存友好,实现了延迟加载;
缺点:不加锁会造成线程不安全问题,加锁又会锁住整个方法,造成性能问题,得不偿失,不推荐。可以尝试使用synchronized锁住代码块(双重检测锁)的方式解决这个问题。
3、双重检测锁式
双重检测锁:当第一次创建单例实例的时候,只有一个线程可以去创建实例,因此不会出现多个线程获取不同实例的情况。
优点:保证了线程安全;
缺点:首次加载稍慢,Java的内存模型在并发过程中具有原子性、可见性、有序性等特点,为了避免“指令重排序”现象和“工作内存与主内存同步延迟”现象,以及一个线程改变的变量值,并不会立刻对其他线程可见等问题,需要加入volatile变量屏蔽指令重排。
4、静态内部类
优点:外部类加载时并不会立即加载内部类,只有当getInstance()方法第一次被调用时,才会导致JVM加载内部类去初始化单例类的实例,这种方式不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
缺点:由于是静态内部类的形式去创建单例的,故外部无法传递参数进去。
5、集合容器
使用Map集合容器统一维护和管理类的实例,将静态创建的实例添加到集合中,通过key获取对应的单例实例即可,推荐使用。
6、枚举实现
是单例模式中最简单的一种实现方式,推荐使用,例如:
public enum SingleInstanceEnum {
INSTANCE;
}
二、单例模式的相关问题
1、反序列化会重新生成对象的问题?
描述:除了枚举、容器之外的其他方式会因为反序列化生成了对象,导致单例模式被破坏。
解决方式:在单例类中添加readResolve()方法,用返回的对象替换反序列化时创建的对象即可。
private Object readResolve() throws ObjectStreamException{
return singleInstance;
}
2、使用反射时会强行调用构造器实例化单例类?
描述:除了枚举、容器之外的其他方式会存在这个问题~
解决方式:修改构造器,让它在创建第二个实例的时候抛异常。
private static volatile boolean flag;
private SingleInstance() {
if (flag) {
flag = false;
} else {
throw new RuntimeException("当前不是首次创建实例!");
}
}
考虑到上述问题,以及采用推荐的静态内部类实现,完整的代码参考如下:
/**
* 静态内部类:当方法被调用时才加载内部类,确保了单例的唯一,实现了懒加载,线程安全
*/
public class SingleInstance4 {
private static volatile boolean flag;
// 防止反射强行调用构造器进行单例类的实例化
private SingleInstance4() {
if(flag){
flag = false;
}else {
throw new RuntimeException("非首次创建该单例的实例!");
}
}
private static class Inner{
private static final SingleInstance4 singleInstance = new SingleInstance4();
}
public static SingleInstance4 getInstance(){
return Inner.singleInstance;
}
// 防止反序列化生成新的对象来破坏单例
private Object readResolve()throws ObjectStreamException {
return Inner.singleInstance;
}
}