单例模式

提到设计模式,很多人会想到单例模式,其实单例模式是很有意思的!

很多场景下,你可能只需要或者必须只要某个类的唯一一个实例,比如:

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。
  • 以及其他我没用过的所有要求只有一个对象的场景。

这时候,就需要单例模式来大显身手。

 

单例模式,顾名思义,就是这个类只能有一个实例,那怎样实现呢?一般情况下,得到一个类实例,就是new一个出来,即调用类的构造方法,所以,只要将构造方法私有,就ok了!?

常见的单例模式有两种:饿汉式单例模式,懒汉式单例模式。

饿汉式:

 

 

懒汉式:

有上图可见单例模式的特点:

1.私有的构造方法;

2.私有的静态引用;

3.公共静态的方法,用于返回实例引用;

 

上述的两种单例模式中,饿汉模式在类加载的时候初始化的实例,但这个实例可能用不到,这就造成了资源的浪费,所以我们比较倾向于懒汉模式,把初始化延迟到使用对象的时候,即在不使用该对象前,不会初始化对象,克服了饿汉式的缺点。但java中,永远不要忘记多线程!!懒汉式模式中,会不会存在线程安全的问题呢?

 

试想这样一种情形,当线程A要得到一个单例对象,且这个对象是第一次(初始化),所以该线程执行if(singleton==null),判断结果是TRUE,就往下执行,但没到初始化的位置,线程B也要得到一个单例对象,它执行if(singleton==null),因为线程A还没到singleton=new Singleton()这一步,所以线程B也判断为TRUE,也会初始化一个实例,所以,两个线程各得到一个实例,违反了单例模式,可见懒汉式还是存在线程安全问题滴。。。。。

怎样解决呢???

第一个方法,也是最简单的,直接对getInstance()接口加锁,即:

 

在getInstance()前加了synchronized,这样就保证只有一个线程可以返回实例。这种处理显然太狠了,上述分析中,我们发现只有在第一次初始化的时候才有线程安全问题,但这种处理导致的后果是,每一次返回对象都要判断锁,显然效率不高,改进的方法是,-----双重检查锁!!!

 

 

上述代码可见,在判断是否第一次初始化的情况下,我们对singleton.getClass()进行的上锁,即我们缩小了锁的力度。貌似情况完美的解决了,但实际上呢?

 

再考虑这样一种情况,但线程A想得到单例实例,且第一次初始化,所以线程A进入了初始化的过程,并上了锁,线程B也想得到单例,但因为实例化比较麻烦,耗费的时间比较久,这时线程A已经有了单例对象引用的地址,却没有真正的实例对象,导致的结果是,线程B判断应经有了实例,直接返回了,可问题是,万一线程A实例化失败了呢,所有的单例得到的就都是“长坏”的实例了。。。。

怎样解决这种问题呢,貌似没办法呢,所以我们采用了另一种方案----内部类

 

这种方法使用内部类来做到延迟加载对象,在初始化这个内部类的时候,JLS(Java Language Sepcification)会保证这个类的线程安全。这种写法最大的美在于,完全使用了Java虚拟机的机制进行同步保证,没有一个同步的关键字。

 

到此为止,我们在Singleton类里完成了“完美”的定义,但使用的时候呢??永远不要忘记,提到对象,就想到类,想到类,也就想到了反射!!就是它!!

 

考虑这样一种情形:

我们利用反射,可以构造出单例的多个实例。我靠,给你单例的接口(getInstance())你不用,你妹的用反射?!所以,为保证单例,我们要避免使用反射创建实例,用类里的借口就行了呗。。。。

 

所以,我们可以总结一下单例使用的注意事项:

1.别用反射构建实例,老老实实用接口;

2.不要断开引用和单例实例的连接,不然单例很容易被gc(垃圾回收)

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值