单例模式

192 阅读2分钟
定义:保证一个类只有一个实例,并提供一个全局的访问点


使用场景:重量级的对象,不需要多个实例,如线程池,数据库连接池

实现方式:

懒汉模式

懒汉模式需要解决的问题:

多线程的并发问题,可以通过synchronize关键字,对创建实例对象进行加锁

多线程情况下的doublecheck问题,当线程没有获得锁的情况下check实例是否存在,在线程获取锁之后再次check实例是否存在

指令重排序问题,通过 volatile 解决指令重排序的问题。

*指令重排:

 instance = new LazySingleton();
 1、分配空间
 2、初始化
 3、引用赋值
 经过指令重排以后可能变为
 1、分配空间
 2、引用赋值
 3、初始化
 在多线程的情况下,比如线程T1、T2,如果发生了指令重排,有可能T2拿到的实例并没有进行初始化
 引发异常

class LazySingleton {    
    // volatile 关键字防止指令重排序    
    private static volatile LazySingleton instance = null;   
    // 私有化构造方法   
    private LazySingleton() {    }    
    public static LazySingleton getInstance() {       
       if (null == instance) {            
          // 对创建对象进行加锁,防止并发            
          synchronized (LazySingleton.class) {               
          // 进行doublecheck                
             if (null == instance) {                    
                instance = new LazySingleton();                
              }            
            }        
         }       
        return instance;    
      }
}


饿汉模式

类加载的初始化阶段就完成了实例的初始化,本质上就是借助jvm类加载机制,保证实例的唯一性以及线程安全。所以饿汉模式的优点是调用效率比较高,但是在类加载时需要创建类的实例,所以效率会比较低。

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

静态内部类模式

1、本质上是利用类的加载机制保证线程安全。

2、只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式。

class InnerClassSingleton {    
     private InnerClassSingleton() {    }   
     private static class InnerClassSingletonHolder {       
       private static InnerClassSingleton instance = new InnerClassSingleton();   
     }    
     public static InnerClassSingleton getInstance() {       
        return InnerClassSingletonHolder.instance;    
     }
}


反射攻击

通过反射破坏单例模式,可以通过反射的机制破会对象的单例模式,通过设置构造参数的accessible为true属性

Constructor<InnerClassSingleton> constructor = InnerClassSingleton.class.getDeclaredConstructor();
// 修改构造参数的属性
constructor.setAccessible(true);
// 通过反射机制获取对象的实例
InnerClassSingleton instance = constructor.newInstance();
// 通过单例模式获取对象实例
InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
System.out.println(instance == instance1);

解决办法:

通过在私有的构造器中,判断定义的全局属性是否已经创建,如果已经创建抛出异常。

private InnerClassSingleton() {    
   if (InnerClassSingletonHolder.instance != null){       
     throw new RuntimeException("单例不允许创建多个实例");    
   }
}