如何避免单例模式被破坏

单例模式几乎每个开发者都会用,但想要写出比较健壮的单例程序,其实并不容易。

这里不再讨论单例的模式的n种写法,仅仅讨论如何避免单例模式被破坏,看下面的一个例子:

public class SimpleSingleton {

    private final static  SimpleSingleton ourInstance = new SimpleSingleton();

    private SimpleSingleton() {
    }

    public static SimpleSingleton getInstance() {
        return ourInstance;
    }
}

这是一个最简单的饿汉式的单例实现,在类进行初始化的时候会安全的创建实例,从而不需要同步。但这么实现,真的能保证任何时候只有一个实例吗?

答案是否定的。

在Java里面,创建对象有4种方式:

(1)new

(2)反射

(3)克隆

(4)反序列化

上面实现的单例,我们通过new确实能保证单例,但是后面的几种方式,都会破坏单例模式。

先说反射的方式,反射在带来的灵活性的同时也破坏了Java封装的特性,通过反射可以访问类里面所有的私有属性和方法。所以反射访问私有构造器是可以非常容易的创建的多个对象实例,从而破坏单例模式。

接着说克隆,这个破坏在大部分时候可以避免,因为想要克隆对象,我们必须实现Cloneable接口,然后重写clone方法,在clone的返回值处,可以返回任何实例。

最后说下序列化和反序列化,如果我们的类没有定义序列化的方法,那么在反序列化的时候,会重新生成一个新的实例,所以这也相当于破坏了单例模式。

最后还有一种不常见的破坏的场景,就是通过我们自定义类加载器来加载类,导致类本身都不是同一个类,这种场景在web项目有多级类加载器的时候比较常见,可以通过一个共用的父加载器来解决这个单例的问题,或者通过需要加载单例的类的时候,使用创建该类本身的加载器去创建,如果不在一个线程里面可以通过线程的上下文来传递类加载器。

我们改下改进后的单例模式:

public class Singleton implements Serializable,Cloneable {

    //在类初始化期间,执行由JVM保证线程安全
    private static Singleton singleton=new Singleton();


    //避免反射和多类加载器破坏
    private Singleton() {
            if (Singleton.singleton != null) {
                throw new InstantiationError("Creating of this object is not allowed.");
            }

    }

    public static Singleton getInstance(){
        return singleton;
    }

    //避免克隆破坏
    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Cloning of this class is not allowed");
//        return super.clone();
    }

//    //避免反序列破坏
    protected Object readResolve() {
        return singleton;
    }



}

正确的编写单例模式,其实是需要很多注意事项的,所以在jdk5之后,推荐使用枚举来创建单例类,通过枚举创建的类其实已经帮我们考虑到了上面的所有问题,不用担心其他的一些情况,JVM内部在创建的时候会自动给枚举的类做特殊处理,从而保证其在各种情况下保持唯一的实例。

原文发布于微信公众号 - 我是攻城师(woshigcs)

原文发表时间:2018-10-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

Java实现单例的难点

有简单又高效的方法可以实现单例模式,但没有一种方式能在任何情况下都确保单例的完整性。

912
来自专栏黑泽君的专栏

c语言基础学习10_文件操作01

============================================================================= ==...

2603
来自专栏java一日一条

你真的会写单例模式吗——Java实现

单例模式可能是代码最少的模式了,但是少不一定意味着简单,想要用好、用对单例模式,还真得费一番脑筋。本文对Java中常见的单例模式写法做了一个总结,如有错漏之处,...

1082
来自专栏JAVA后端开发

activiti通过扩展点重写节点行为

在activit项目中,有时需要重写节点的behaviour,但如果将代码反编译,会为后续升级,及项目打包带为不方便。   其实 acitivit已经提供了扩...

3575
来自专栏程序员互动联盟

【编程基础】C语言FILE结构体以及缓冲区深入探讨

在C语言中,用一个指针变量指向一个文件,这个指针称为文件指针。通过文件指针就可对它所指的文件进行各种操作。 定义文件指针的一般形式为: FILE *...

4417
来自专栏逍遥剑客的游戏开发

UE4学习笔记: Functions

35610
来自专栏肖洒的博客

爬虫入门(四):urllib2

主要使用python自带的urllib2进行爬虫实验。 写在前面的蠢事: 本来新建了一个urllib2.py便于好认识这是urllib2的实验,结果始终编译不...

993
来自专栏琯琯博客

PHP 优化技巧

3375
来自专栏跟着阿笨一起玩NET

sql 多条件查询的一种简单的方法

4472
来自专栏Albert陈凯

Hadoop数据分析平台实战——070深入理解MapReduce 02(案例)离线数据分析平台实战——070深入理解MapReduce 02

离线数据分析平台实战——070深入理解MapReduce 02 Shuffle阶段说明 shuffle阶段主要包括map阶段的combine、group、sor...

3136

扫码关注云+社区

领取腾讯云代金券