单例设计模式

基本概念
  1. 定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例
  2. 违背单一职责原则:既负责实现对象逻辑,由负责生成管理对象。
分类
饿汉式

饿汉式:类加载到内存后,就会实例化。JVM线程安全。缺点:不论是否需要使用,都会加载

public class Mgr01 {
	
    private static  final  Mgr01 INSTANCE = new Mgr01();
	//私有构造器,无法使用new来创造实例
    private Mgr01(){

    }
    private static Mgr01 getInstance(){
        return  INSTANCE;
    }
}

public class Mgr02 {

    private static  final Mgr02 INSTANCE;

    //利用静态代码块来初始化对象
    static {
        INSTANCE = new Mgr02();
    }

    private Mgr02(){

    }
    private static Mgr02 getInstance(){
        return  INSTANCE;
    }

}
懒汉式
  1. lazy loading 懒加载,只有使用的时候才创造实例
方法加互斥锁

不推荐!调用该方法时都要判断是否获取锁

    private static Mgr04 INSTANCE;

    private Mgr04(){

    }

    private synchronized static Mgr04 getInstance(){
        if(INSTANCE  != null){
            INSTANCE = new Mgr04();
        }
       return  INSTANCE;
    }
}
dcl(double check lock)
public class Mgr03 {
	
    //volatile保重操作的有序性
    private volatile static  Mgr03 INSTANCE;

    private Mgr03(){

    }
    //双重检查

    /**
     * 当某个线程通过第一个if判断后还没有获取锁,另一个线程先获取锁new了对象,如果没有第二个if判断就会出现对象不同的情况
     * @return
     */
    private static Mgr03 getInstance(){
        if(INSTANCE  != null){
            //1
            synchronized (Mgr03.class){
                if(INSTANCE  != null) {
                    INSTANCE = new Mgr03();
                    //执行了三步1. 分配内存空间2,初始化对象3,设置instance指向刚分配的内存地址
                    //在一些JIT编译器 2,3两步可能会被重排,即先分配地址后初始化对象
                    //当一个线程分配完地址后,另一个线程判断第一个if就为ture,这时候返回一个未初始化对象的地址
                    //改进1.volatile(不允许重排)2.允许重排(mgr05)

                }
            }
        }
        return  INSTANCE;
    }
静态内部类(推荐)
/**
 * @description: 静态内部类的写法,jvm保重单例,加载外部类的时候不会加载内部类,实现懒加载(内部类只有使用的时候才加载)
 * @author: liSen
 * @time: 2021/7/27 23:16
 */
public class InnerStaticSingleton{

    /**
     * 静态初始化器,JVM保证线程安全
     */
    private static class SingletonHolder {
        private static InnerStaticSingleton instance = new InnerStaticSingleton();
    }

    /**
     * 私有构造函数
     */
    private InnerStaticSingleton(){

    }

    /**
     * 获取对象实例方法
     * @return
     */
    public static InnerStaticSingleton getInstance(){
        return SingletonHolder.instance;
    }
}

枚举
/**
 * 不但解决线程安全,防止反序列化(枚举类没有公共构造方法)
 */
public enum EnumSingleton {

    /**
     * 实例对象
     */
    INSTANCE;

    private Object data;

    /**
     * 私有构造器实现初始化逻辑
     */
    private EnumSingleton(){
        System.out.println("hhhh");
    }

    public Object getData(){
        return data;
    }

    public void setData(Object data){
        this.data = data;
    }

    public static EnumSingleton getInstance(){return INSTANCE;}
}
扩展

指定产生实例的个数

public class Emperor {

    private static final int maxNum = 2; //控制产生实例的个数

    private static ArrayList<Emperor> emperors = new ArrayList<>();

    private  String emperorName;//皇帝姓名,不能为static,当为static的时候多个emperor共享同一属性

    static {//初始化实例
        for (int i = 0; i < maxNum; i++) {
            emperors.add(new Emperor("皇帝"+ i));
        }
    }

    private Emperor(){

    };

    private Emperor(String name){
        emperorName = name;
    };

    public static Emperor getInstance(int num){
        return emperors.get(num);
    }

    public  void say(){
        System.out.println(emperorName);
    }

}
public class Minister {
    public static void main(String[] args) {
        //定义5个大臣
        int ministerNum =5;
        for(int i=0;i<ministerNum;i++){
            Emperor emperor = Emperor.getInstance(new Random().nextInt(2));
            System.out.print("第"+(i+1)+"个大臣参拜的是:");
            emperor.say();
        }
    }
}

单例对象的攻击方式

反射

​ 利用jdk反射ApI,修改单例构造函数的访问权限,调用构造函数。

//反射破坏访问
//获取Class类
Class clazz = InnerStaticSingleton.class;
//返回类的空参构造函数
Constructor constructor = clazz.getDeclaredConstructor();
//暴力访问,调用的时候不检查访问权限
constructor.setAccessible(true);
//生成实例对象
InnerStaticSingleton innerStaticSingleton = (InnerStaticSingleton) constructor.newInstance();
//@4554617c
System.out.println(innerStaticSingleton);		
序列化

​ 将单例对象实例以字节流的方式写入文件中,然后再读取字节流,反序列化生成对象实例。

//序列化破坏(原始类要实现Serializable接口)
FileOutputStream outputStream = new FileOutputStream("a.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(instance);
objectOutputStream.flush();
objectOutputStream.close();

FileInputStream inputStream = new FileInputStream("a.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
InnerStaticSingleton instance3 = (InnerStaticSingleton) objectInputStream.readObject();
//@27bc2616
System.out.println(instance3);
调用对象clone方法
//clone攻击(原始类要实现Cloneable接口并实现clone方法
InnerStaticSingleton cloneInstance = (InnerStaticSingleton) instance.clone();
//@3941a79c
System.out.println(cloneInstance);
是否考虑

​ 如果你的单例类作为工具类库提供给别人使用就要考虑,如果自己使用一般不需要。

修复

反射

/**
 * 私有构造函数:只要调用的时候不为null说明是非法调用
 */
private InnerStaticSingleton(){
    //修复反射bug
    if(SingletonHolder.instance != null){
        throw  new RuntimeException();
    }
}

序列化

/**
 * 防止序列化破坏(readObject的时候如果类重写了该方法就调用该方法的内容)
 * @return
 */
public Object readResolve(){
    return getInstance();
}

克隆

/**
 * 重写clone方法,直接返回创建好的对象
 * @return
 * @throws CloneNotSupportedException
 */
@Override
protected Object clone() throws CloneNotSupportedException {
    return getInstance();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值