单例模式的特点:
- 首先,单例模式使类在程序生命周期的任何时刻都只有一个实例,
- 然后,单例的构造函数是私有的,外部程序如果想要访问这个单例类的话,
- 必须通过
getInstance()
来请求(注意是请求)得到这个单例类的实例。
有的时候,总是容易把全局变量和单例模式给弄混了,下面就剖析一下全局变量
和单例模式
相比的缺点:
首先,全局变量呢就是对一个对象的静态引用,全局变量确实可以提供单例模式实现的全局访问这个功能。但是,它并不能保证您的应用程序中只有一个实例,同时,在编码规范中,也明确指出,应该要少用全局变量,因为过多的使用全局变量,会造成代码难读,还有就是全局变量并不能实现继承(虽然单例模式在继承上也不能很好的处理,但是还是可以实现继承的)。而单例模式的话,其在类中保存了它的唯一实例,这个类,它可以保证只能创建一个实例,同时,它还提供了一个访问该唯一实例的全局访问点。
为何要使用双重检查锁定呢?
考虑这样一种情况,就是有两个线程同时到达,即同时调用getInstance()
,
此时由于singleton == null
,所以很明显,两个线程都可以通过第一重的singleton == null
,进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重 singleton == null ,而另外的一个线程则会在lock
语句的外面等待。而当第一个线程执行完 new Singleton()
语句后,便会退出锁定区域,此时,第二个线程便可以进入lock
语句块,此时,如果没有第二重singleton == null
的话,那么第二个线程还是可以调用new Singleton()
语句,这样第二个线程也会创建一个Singleton
实例,这样也还是违背了单例模式的初衷的,所以这里必须要使用双重检查锁定。
细心的朋友一定会发现,如果我去掉第一重singleton == null
,程序还是可以在多线程下完好的运行的,考虑在没有第一重singleton == null
的情况下,当有两个线程同时到达,此时,由于lock
机制的存在,第一个线程会进入lock
语句块,并且可以顺利执行new Singleton()
,当第一个线程退出lock
语句块时, singleton
这个静态变量已不为null
了,所以当第二个线程进入lock
时,还是会被第二重singleton == null
挡在外面,而无法执行new Singleton()
.
所以在没有第一重singleton == null
的情况下,也是可以实现单例模式的。
那么为什么需要第一重singleton == null
呢?
这里就涉及一个性能问题了,因为对于单例模式的话,new Singleton()
只需要执行一次就 OK 了,而如果没有第一重singleton == null
的话,每一次有线程进入getInstance()
时,均会执行锁定操作来实现线程同步,这是非常耗费性能的,而如果我加上第一重singleton == null
的话,那么就只有在第一次,也就是singleton ==null
成立时的情况下执行一次锁定以实现线程同步,而以后的话,便只要直接返回Singleton
实例就 OK 了而根本无需再进入lock
语句块了,这样就可以解决由线程同步带来的性能问题了。