定义:保证一个类只有一个实例,并提供一个全局的访问点
使用场景:重量级的对象,不需要多个实例,如线程池,数据库连接池
实现方式:
懒汉模式
懒汉模式需要解决的问题:
多线程的并发问题,可以通过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("单例不允许创建多个实例");
}
}