设计模式之单例模式

本文详细介绍了设计模式中的单例模式,包括其应用场景、优缺点、特点以及7种实现方式,如懒加载、饿汉式、静态内部类实现等。同时,讨论了如何通过反射和序列化破解单例,并提供了防止破解的建议。
摘要由CSDN通过智能技术生成

单例模式

什么是单例模式:JVM中对象只有一个实例的存在

单例的应用场景:

  1. 项目中定义的配置文件
  2. Servlet对象默认为单例
  3. 线程池、数据库连接池(这里体现了复用机制)
  4. Spring中的Bean对象
  5. 一个网站的计数器
  6. JVM内置缓存框架(定义一个单例的HashMap)
  7. 枚举(最安全的单例)

单例模式的优缺点:

  • 优点:能够节约当前堆内存空间,不需要频繁new对象,能够快速访问;
  • 确定:当多个线程访问同一个单例对象的时候可能存在线程安全问题(懒加载的时候)

单例模式特点

  1. 构造方法私有化
  2. 实例化的变量引用私有化
  3. 获取实例的方法共有

单例的写法(7种):

绝非像鲁迅笔下“茴的4种写法”那样毫无意义

  1. 懒加载(线程不安全版)
  2. 懒加载(线程安全版)
  3. 懒加载(双重检验锁)
  4. 饿汉式
  5. 静态代码块(饿汉式)
  6. 静态内部类(懒汉式)
  7. 枚举实现

懒加载(线程不安全版)

懒汉式基本概念:当真正获取到该对象时,才会创建该对象

public class Singleton01 {
    //实例化的变量引用私有化
    private static Singleton01 singleton = null;

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

    }

    public static Singleton01 getSingleton() {
        if (singleton == null) {
            singleton = new Singleton01();
        }
        return singleton;
    }

    public static void main(String[] args) {
        Singleton01 singleton1 = Singleton01.getSingleton();
        Singleton01 singleton2 = Singleton01.getSingleton();
        System.out.println(singleton1 == singleton2);
    }
}

懒加载(线程安全版)

问题:什么时候会出现线程安全问题?

当第一次new出该对象 并赋值到私有引用变量singleton 后面的所以线程直接获取该singleton对象,不需要重复new

public class Singleton02 {
    //实例化的变量引用私有化
    private static Singleton02 singleton = null;

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

    }

    //  创建和读取对象都需要获取Singleton01 锁
    public static synchronized Singleton02 getSingleton() {
        if (singleton == null) {
            singleton = new Singleton02();
        }
        return singleton;
    }

    public static void main(String[] args) {
        Singleton02 singleton1 = Singleton02.getSingleton();
        Singleton02 singleton2 = Singleton02.getSingleton();
        System.out.println(singleton1 == singleton2);
    }
}

懒加载(双重检验锁)

双重校验锁(DCL)能够保证线程安全,只会在创建单例兑现时候上锁,获取该单例对象不会上锁,效率比较高。

volatile(避免了重排序,new操作非原子性操作,防止多个线程进行重排序)

public class Singleton03 {
    //实例化的变量引用私有化
    private static volatile Singleton03 singleton = null;

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

    }

    //  创建和读取对象都需要获取Singleton01 锁
    public static Singleton03 getSingleton() {
        if (singleton == null) {
            synchronized (Singleton03.class) {
                if (singleton == null) {
                    singleton = new Singleton03();
                }
            }
        }
        return singleton;
    }

    public static void main(String[] args) {
        Singleton03 singleton1 = Singleton03.getSingleton();
        Singleton03 singleton2 = Singleton03.getSingleton();
        System.out.println(singleton1 == singleton2);
    }
}

饿汉式

提前创建单例对象,优点:先天性线程安全 缺点:占内存

public class Singleton04 {
    // 当我们class被加载时,就会提前创建singleton对象
    private static Singleton04 singleton = new Singleton04();

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

    }

    public static Singleton04 getSingleton() {
        return singleton;
    }

    public static void main(String[] args) {
        Singleton04 singleton1 = Singleton04.getSingleton();
        Singleton04 singleton2 = Singleton04.getSingleton();
        System.out.println(singleton1 == singleton2);
    }
}

静态代码块(本质就是饿汉式)

public class Singleton05 {
    // 当我们class被加载时,就会提前创建singleton对象
    private static Singleton05 singleton = null;

    static {
        singleton = new Singleton05();
        System.out.println("static执行");
    }

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

    }

    public static Singleton05 getSingleton() {
        return singleton;
    }

    public static void main(String[] args) {
        Singleton05 singleton1 = Singleton05.getSingleton();
        Singleton05 singleton2 = Singleton05.getSingleton();
        System.out.println(singleton1 == singleton2);
    }
}

静态内部类(懒汉式)

静态内部类,在Spring框架源码中,经常发现使用静态内部类

其虽然是懒汉式,但具有先天性线程安全

public class Singleton06 {

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

    }

    private static class SingletonHolder {
        private static Singleton06 singleton = new Singleton06();
    }

    public static Singleton06 getSingleton() {
        return SingletonHolder.singleton;
    }

    public static void main(String[] args) {
        Singleton06 singleton1 = Singleton06.getSingleton();
        Singleton06 singleton2 = Singleton06.getSingleton();
        System.out.println(singleton1 == singleton2);  //true
    }
}

枚举实现

枚举属于目前最安全的单例,不能够被反射(这里指反射创建,可以参考Constructor的newInstance源码,如果类型是Enum会抛出一个异常) 不能序列化和反序列化保证单例

public enum Singleton03 {
    INSTANCE;

    public void getInstance() {
        System.out.println("<<<getInstance>>>");
    }
}

创建对象的几种方式

1.直接new对象

2.采用克隆对象

3.使用反射创建对象

4.序列化与反序列化

如何破解单例

  1. 反射破解单例(懒加载的情况下)
public class Singleton01 {
    //实例化的变量引用私有化
    private static volatile Singleton03 singleton = null;

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

    }

    //  创建和读取对象都需要获取Singleton01 锁
    public static Singleton03 getSingleton() {
        if (singleton == null) {
            synchronized (Singleton03.class) {
                if (singleton == null) {
                    singleton = new Singleton03();
                }
            }
        }
        return singleton;
    }

    public static void main(String[] args) {
		 Class<?> aClass = Class.forName("top.beau.Singleton01");
		 Singleton01 singleton03 = (Singleton01) aClass.newInstance();
      	   // 使用反射破解单例
//        Singleton01 singleton01 = Singleton01.getSingleton01();
//        Singleton01 singleton02 = Singleton01.getSingleton01();
//        // 使用反射破解单例

//        System.out.println(singleton02 == singleton03);  false
    }
    }
}

如何防止反射单例被破解

  1. 不使用懒汉式(静态内部类除外)
  2. 反射创建单例置于 new获取单例对象之后
2、序列化破解单例

序列化:将对象转换成二进制的形式

反序列化:从硬盘/网络读取二进制变为对象

public class Singleton02 implements Serializable {
    private static Singleton02 singleton = new Singleton02();


    public static Singleton02 getSingleton() {
        return singleton;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 1.将对象序列化存入到本地文件中
        FileOutputStream fos = new FileOutputStream("d:/code/a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Singleton02 singleton1 = Singleton02.getSingleton();
        oos.writeObject(singleton1);
        oos.close();
        fos.close();
        System.out.println("----------从硬盘中反序列化对象到内存中------------");
        //2.从硬盘中反序列化对象到内存中
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/a.txt"));
        // 从新获取一个新的对象
        Singleton02 singleton2 = (Singleton02) ois.readObject();
        System.out.println(singleton1 == singleton2);
    }
//如何防御反序列化创建新的对象
    //java层面 封装好了
	//    readObject 通过反射技术 序列化类中存在readResolve方法 保证单例
  private Object readResolve() throws ObjectStreamException {
     return singleton;
  }
}

  
 序列化类中存在readResolve方法 保证单例
原理:

1. 调用readObject() 

2. 执行readObject0();

3. Switch 判断 
![请添加图片描述](https://img-blog.csdnimg.cn/73177be3dcfa405fa919828f7991bfa0.png)

判断反序列化类中如果存在readResolve方法 则通过反射机制调用readResolve方法返回相同的对象
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值