单例模式

单例模式是设计模式中使用最为普遍的模式之一,它是一种对象创建模式,用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例。在Java语言中,这样的行为能带来两大好处:

(1) 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。

(2)由于new的操作次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。

因此对于系统的关键组件和被频繁使用的对象,使用单例模式便可以有效地改善系统的性能。

饿汉式:Single一进入内存,就已经创建好了对象。线程安全的。

懒汉式:对象是方法被调用时,才初始化,也叫作对象的延时加载。线程不安全的。

对懒汉式线程不安全的改进:

getInstance()方法必须是同步的,否则在多线程环境下,当线程1正新建单例时,完成赋值操作前,线程2可能判断instance为null,故线程2也将启动新建单例的程序,而导致多个实例被创建,故同步关键字是必须的。

但同时带来的问题是由于引入了同步关键字,因此在多线程环境中,它的耗时要远远大于饿汉式单例模式

对懒汉式引入同步关键字降低性能的改进1:

双重判断 减少锁的读取次数

改进1的问题:指令重排,看似简单的一段赋值语句:s= new Single1(),但是很不幸它并不是一个原子操作,其实际上可以抽象为下面几条JVM指令:

memory =allocate();    //1:分配对象的内存空间 

ctorInstance(memory);  //2:初始化对象 

instance =memory;     //3:设置s指向刚分配的内存地址 

上面操作2依赖于操作1,但是操作3并不依赖于操作2,所以JVM是可以针对它们进行指令的优化重排序的,经过重排序后如下:

memory =allocate();    //1:分配对象的内存空间 

instance =memory;     //3:s指向刚分配的内存地址,此时对象还未初始化

ctorInstance(memory);  //2:初始化对象

可以看到指令重排之后,s指向分配好的内存放在了前面,而这段内存的初始化被排在了后面。

在线程A执行这段赋值语句,在初始化分配对象之前就已经将其赋值给s引用,恰好另一个线程进入方法判断s引用不为null,然后就将其返回使用,导致出错。

解决方案:

以关键字volatile修饰之后,就会阻止JVM对其相关代码进行指令重排,这样就能够按照既定的顺序指执行。

对懒汉式引入同步关键字降低性能的改进2:

单例模式使用内部类来维护单例的实例,当Single1被加载时,其内部类并不会被初始化,故可以确保当SingletonHolder类被载入JVM时,不会初始化单例类,而当getInstance()方法被调用时,才会加载SingletonHolder,从而初始化instance。同时,由于实例的建立是在类加载时完成的,故天生对多线程友好,getInstance()方法也不需要使用同步关键字。

通常情况下,用以上方式实现的单例已经可以确保在系统中只存在唯一实例了,但仍然有例外的情况,可能导致系统生成多个实例,比如,在代码中,通过反射机制,强行调用单例类的私有构造函数,生成多个实例。还有一种合法的方法可能导致系统出现多个单例类的实例。

一个可以被串行化的单例:

当去掉Single1代码中readResolve()函数时,返回结果为false,说明s和s1指向了不同的实例,在反序列化后生成了多个对象实例。而加上readResolve()函数时,返回结果为true。说明即便经过反序列化后,仍然保持了单例的特征。事实上,在实现了私有的readResolve()方法后,readObject()已经形同虚设,它直接使用readResolve()替换了原本的返回值,从而在形式上构造了单例。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值