单例没你想的那么简单
单例是最简单的几种设计模式之一,但它远没有你想的那么简单!
==============
应用
简单的提一下,哪里需要使用单例:线程池、缓存、网络等等需要保持全局作用域且唯一的对象。
但是这里需要提一下,什么不适合申明为单例:View。View是比较重的对象,占内存较普通对象更多,而View更可能引用其他资源,而导致其他资源也无法被释放。
懒汉:
private static NetWork instance;
public static NetWokr getInstance({
if(instance == null){
instance = new NetWork();
}
return instance;
)
饿汉
private static NetWork instance = new NetWork();
public static NetWokr getInstance(){
return instance;
)
单例简单,单例的两种申明方式大家也都知道。但知道如何使用这两种申明方式的可就不多了。
最普通的一种解释是:
如果这个对象并不着急使用,或者这个对象占据资源较多的话,就使用懒汉来延迟加载。
如果这个资源创建时间很长,就提前创建。
其实这个解释并不能让我很满意,哪些对象占据资源特别多?创建时间特别长的对象又是什么?真正遇到这样的场景其实并不多,所以大部分时候我们还是看心情来……
懒汉与饿汉的区别
懒汉可以保证我们的单例在需要的时候才被初始化。这是一个优点,也是优于单纯的静态变量的一个点。
但懒汉在多线程环境下,有可能会出现问题。
在未加锁的情况下,两个线程同时 getInstance(),然后就创建新对象。这就导致了系统中有两个单例存在,这是很危险的一件事,容易导致非常奇怪而且找不到问题的Bug。
解决这个问题有两个简单办法:
在getInstance方法加synchronized 修饰。但这也可能因为多线程的同步问题拖垮性能。
public static synchronized NetWokr getInstance({ if(instance == null){ instance = new NetWork(); } return instance; )
使用饿汉方式。但也有问题,也许初始化了的单例,一直都没有被用到。
利用双重检查加锁机制实现高效的多线程单例
private volatile static NetWork instance;
public static NetWokr getInstance({
//检查是否未创建,如果未创建才加锁
if(instance == null){
//只有第一次未创建才会进入到这里
synchronized(NetWork.class)
{
//进入后再检查一次,如果为Null才创建实例
if(instance == null){
instance = new NetWork();
}
}
}
return instance;
)
这样写是不是有种眼前一亮的感觉?
单例与静态类的区别
单例和静态类的区别一直都是大多数人纠结的问题,因为乍一看,他们就是一样的嘛。
下面总结一下单例与静态类的区别:
- 静态类在最开始就初始化了,单例可以延迟加载
全局变量并不能保证只有一个实例
在多个类加载器同时存在的情况下,就有可能导致多个单例存在的奇怪现象。
单例依然是可以继承类实现接口,依然可以实现面向对象特性。而静态类就不可以了。
那这么说,静态类岂不完全没有市场了?
不是的,就拿工具类来说,静态类就比单例合适。
当所有的方法都被申明为静态方法以后,我们用这个类是不需要创建对象的消耗内存的。
而要使用单例,反而还要多开销一个内存。
单例的缺点
上面都在说单例的优点了,最后就提一下单例的缺点吧。为什么只是提一下呢?因为单例真的比较优秀,不太好挑刺啊。
缺点1: 因为是静态的,所以会一直常驻在内存中,如果这个单例的使用频率很低,就是一种浪费了。自己掌握其申明周期是一个不错的选择。
缺点2: 申明了两个单例以上,这个Bug是致命的,因为你很难会想到问题在这上面,就类似如香蕉脱了衣服把自己滑到这种事情。
缺点3: java的GC和引用机制,非常用以导致OOM,单例的生命周期是整个应用的生命周期,所以单例所引用的对象也将会是整个应用的生命周期。如果使用不当,就将导致内存溢出。