设计模式之单例模式

本文介绍了设计模式中的单例模式,包括懒汉模式、饿汉模式、静态内部类实现,以及如何防止反射攻击。同时,枚举类型的单例特性被提及,解决了线程安全和序列化问题。此外,还探讨了单例模式在Spring和JDK源码中的应用,如Runtime、ProxyFactoryBean和SingletonBeanRegistry等。
摘要由CSDN通过智能技术生成

模式定义:
        保证一个类只有一个实例,并且提供一个全局访问点

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

1.懒汉模式

        延迟加载, 只有在真正使用的时候,才开始实例化。

        基本实现:

public class LazySingletonTest {
    public static void main(String[] args) {
        LazySingleton instance1 = LazySingleton.getInstance();
        LazySingleton instance2 = LazySingleton.getInstance();
        System.out.println(instance1==instance2);


    }
}

class LazySingleton{
    private static LazySingleton instance;
    private LazySingleton(){

    }
    public static LazySingleton getInstance(){
        if(instance==null){
            instance = new LazySingleton();
        }
        return instance;
    }
}

单线程情况下没问题,但是多线程下就存在线程安全问题

public class LazySingletonTest {
    public static void main(String[] args) {

        new Thread(()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();
        new Thread(()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();
    }
}

class LazySingleton{
    private static LazySingleton instance;
    private LazySingleton(){

    }
    public static LazySingleton getInstance(){
        if(instance==null){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazySingleton();
        }
        return instance;
    }
}

第一步优化,加锁并双重检查

public  static LazySingleton getInstance(){
    if(instance==null){
        synchronized (LazySingleton.class){
            if(instance==null){
                instance = new LazySingleton();
            }
        }
    }
    return instance;
}

第二部优化,防治指令重排,造成并发情况下出现空指针,加入volatile关键字。

OK,保证线程安全的懒汉模式就是:

class LazySingleton{
    private volatile static LazySingleton instance;
    private LazySingleton(){

    }
    public  static LazySingleton getInstance(){
        if(instance==null){
            synchronized (LazySingleton.class){
                if(instance==null){
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

 2.饿汉模式

类加载的 初始化阶段就完成了 实例的初始化 。本质上就是借助于jvm类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)。

实现:

class HungrySingleton{
    private static  HungrySingleton instance = new HungrySingleton();
    private HungrySingleton(){

    }
    public static HungrySingleton getInstance(){
        return instance;
    }
}

3.静态内部类

  • 本质上是利用类的加载机制来保证线程安全
  • 只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式。
class InnerClazzSingleton{
    private static class InnerClassHolder{
        private static InnerClazzSingleton instance = new InnerClazzSingleton();
    }
    private InnerClazzSingleton(){

    }
    public static InnerClazzSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

4.反射攻击

对于上述的懒汉、恶汉、静态内部类的单例模式,均可以通过反射破解,以静态内部类为例:

public class InnerClazzSingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<InnerClazzSingleton> constructor = InnerClazzSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        InnerClazzSingleton innerClazzSingleton = constructor.newInstance();

        InnerClazzSingleton instance = InnerClazzSingleton.getInstance();
        System.out.println(innerClazzSingleton==instance);
    }
}

class InnerClazzSingleton{
    private static class InnerClassHolder{
        private static InnerClazzSingleton instance = new InnerClazzSingleton();
    }
    private InnerClazzSingleton(){

    }
    public static InnerClazzSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

  •  对于恶汉模式和静态内部类可以用以下方式防止反射破解,但是懒汉模式不能防止反射破解
class InnerClazzSingleton{
    private static class InnerClassHolder{
        private static InnerClazzSingleton instance = new InnerClazzSingleton();
    }
    private InnerClazzSingleton(){
        if(InnerClassHolder.instance!=null){
            throw new RuntimeException("单例不允许多个实例");
        }
    }
    public static InnerClazzSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

5.枚举类型

天然不支持反射创建对应的实例,且有自己的反序列化机制。

利用类加载机制保证线程安全。

public enum  EnumSingleton {
    INSTANCE;
}

class EnumSingletonTest{
    public static void main(String[] args) {
        EnumSingleton instance = EnumSingleton.INSTANCE;
        EnumSingleton instance1 = EnumSingleton.INSTANCE;
        System.out.println(instance==instance1);
    }
}

6.序列化

可以利用 指定方法来替换从反序列化流中的数据,枚举类型天然支持反序列化

public class InnerClazzSingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
        InnerClazzSingleton instance = InnerClazzSingleton.getInstance();

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerClazzSingleton"));
        oos.writeObject(instance);
        oos.close();

        /*ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerClazzSingleton"));
        InnerClazzSingleton innerClazzSingleton = (InnerClazzSingleton) ois.readObject();
        System.out.println(innerClazzSingleton==instance);*/


    }
}

class InnerClazzSingleton implements Serializable {

    //static final long serialVersionUID = 42L;
    private static class InnerClassHolder{
        private static InnerClazzSingleton instance = new InnerClazzSingleton();
    }
    private InnerClazzSingleton(){
        if(InnerClassHolder.instance!=null){
            throw new RuntimeException("单例不允许多个实例");
        }
    }
    public static InnerClazzSingleton getInstance(){
        return InnerClassHolder.instance;
    }
    /*Object readResolve() throws ObjectStreamException {
        return InnerClassHolder.instance;
    }*/
}

序列化后输出到文件里

反序列化后可以看到和我们通过getInstance()拿到的对象不一致,单例变成了多例

加入版本号和readResolve防范

static final long serialVersionUID = 42L;
Object readResolve() throws ObjectStreamException {
    return InnerClassHolder.instance;
}

 7.源码中的应用

 // Spring & JDK
java.lang.Runtime
org.springframework.aop.framework.ProxyFactoryBean
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
org.springframework.core.ReactiveAdapterRegistry

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值