破坏单例模式

推荐语:单例模式是 Java 中最简单的一种设计模式,也是最常用,最为人们所津津乐道的一种设计模式。单例模式的实现需要关注性能和安全问题,如用之不慎,就可能会对软件带来许多隐患。

时间:某天下午
地点:软件实验室
故事主人公:蔡同学:软件专业的学生。
牛老师:蔡同学的班主任。
背景:蔡同学在实验室学习,牛老师路过看到后便进去与他交流,了解下学习情况。

牛:“小蔡,还在学习呢,真是用功呀!”
蔡:“是呀!现在就业环境太卷了,没办法,不用功就会落后的。”
牛:“也是,现在社会竞争这么激烈,多学点总是好的。你在看什么呢?”
蔡:“哦,我在看单例模式呢,觉得挺有意思的。”
牛:“哦?是嘛!那你跟我讲讲单例模式呢”
蔡:“嘿嘿,我看的还不多,只了解一些。单例模式就是一个类只能有一个实例,并提供对该实例的全局访问点。实现方式有饿汉式、懒汉式、静态内部类、双重检测锁DCL。我刚看了DCL,这种方式既实现懒加载,又保证线程安全,感觉又学到了不少。”
牛:“嗯,你说你学了DCL,那你思考下DCL有什么问题没有。”
蔡:“问题?我觉得挺完美的了,想不出有什么问题。但老师你这么问,那它肯定是有问题的。”
牛老师微微一下,接着说:“DCL是一种比较好的单例实现方式,让我看一下你的DCL代码”

public class Singleton{
    private volatile static Singleton singleton;
    private Singleton() {}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}	

牛老师接着说:“Effective Java第3条中提醒到:享有特权的的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。我现在给你演示下反射破坏DCL单例。”

public static void main(String[] args) throws Exception {
        Singleton singleton1 = Singleton.getSingleton();
        System.out.println(singleton1); //com.singleton.Singleton@6ff3c5b5

        // 反射获取构造器
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        // 得到构造器的访问权
        constructor.setAccessible(true);
        Singleton singleton2 = constructor.newInstance();
        System.out.println(singleton2); //com.singleton.Singleton@3764951d
}

蔡:“是哦,两个实例对象不一样,DCL确实被破坏了。反射通过构造器来获得实例,那就得从构造器这边下手处理。在构造器里加个实例的判断,若已经创建过实例,则抛出异常。”

private Singleton() {
        synchronized (Singleton.class) {
            if(singleton != null) throw new RuntimeException("不可创建多个实例");
        }
}

牛:“你说的没错,如果要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例时抛异常。不过你的改进还是有问题,如果我并不用Singleton.getSingleton(),而是直接通过反射的构造器获取实例,那你的成员变量singleton一直都是null的,你的判断还是无效的。”

  Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton singleton2 = constructor.newInstance();
    Singleton singleton3 = constructor.newInstance();
    System.out.println(singleton2); //com.singleton.Singleton@6ff3c5b5
    System.out.println(singleton3); //com.singleton.Singleton@3764951d

蔡:“不管是Singleton.getSingleton(),还是反射,都是要通过构造器来实例化的,那加个标识来计数,不让构造器二次构造实例。”

public class Singleton{
    private volatile static Singleton singleton;
    private static int count = 0;

    private Singleton() {
        synchronized (Singleton.class) {
            if(count > 0) throw new RuntimeException("不可创建多个实例");
            count++;
        }
    }
    public static Singleton getSingleton() {...}
}

牛:“是这样,但是还有个问题,既然反射能够获得构造器,那也能用Singleton.class.getDeclaredFields()获得成员变量,获取成员变量后,再修改成员变量值,还是会破坏。”

Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton singleton2 = constructor.newInstance();
    System.out.println(singleton2); //com.singleton.Singleton@6ff3c5b5
    
    //获取属性
    Field count = Singleton.class.getDeclaredField("count");
    count.setAccessible(true);
    //将count再修改为0
    count.set(singleton2, 0);
    Singleton singleton3 = constructor.newInstance();
    System.out.println(singleton3); //com.singleton.Singleton@4b1210ee
    

蔡:“啊!这。。。不怪程序不严谨,只怪反射太霸道。”
牛:“不仅如此,我们上面都是通过构造器来实例化的,如果不通过构造器的方式实例化对象,如克隆,序列化,也会破坏单例模式。比如克隆,Singleton 实现Cloneable接口后。”

		Singleton singleton1 = Singleton.getSingleton();
        System.out.println(singleton1); //com.singleton.Singleton@6ff3c5b5
        Singleton singleton2 = (Singleton) singleton1.clone();
        System.out.println(singleton2); //com.singleton.Singleton@3764951d

蔡:“原以为单例模式很简单,没先到其中有这么多坑啊。有没有一种更好的方案呢。”
牛:“哈哈,是啊。单例模式人们研究出不下8种实现方式,除了你说的懒汉式、饿汉式、静态内部类、DCL,还有容器法、ThreadLocal实现、CAS实现、枚举等。这里面枚举实现单例是最为推荐的,它实现简单,又能避免上面的反射,克隆,序列化等的破坏。但其仍有一些缺点,如:并非懒加载,无法继承等。”
蔡:“没想到啊,本以为简简单单的单例模式,竟然也有这么多学问。真是应了那句话:看似简单的东西往往背后蕴含许多深意。通过这个案例,我也总结出来两点:
一是软件的功能有时是鱼与熊掌的问题,选择其一,势必要牺牲其二;
二是软件的安全发展总是在攻防对决之间,螺旋上升,曲折发展。”
牛:“哈哈哈!总结的不错,好了,你继续学习吧!”
说完,牛老师便出了门。小蔡同学低头沉思了一会,便又扑在了电脑前。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值