抬头仰望星空,是否能发现自己的渺小。

伪斜杠青年

人们总是混淆了欲望和理想

重新理解一下单例模式-单例为什么要这样写

以前刚毕业时候,面试总会问到单例,但是事实上只是网上看了下最终结论,但是从未去了解为什么,面对网上众多解释,今天在一书上找到了答案。

单例,毫无疑问是指单一实例,但是解法却有很多,我记得网上看貌似有11种的,但是用处就不知道了。让我们来慢慢分析下演变过程吧。

不好的解法(一):

一开始没想啥,就直接写,就希望在null的时候去创建,不为空就直接返回。

public class Single {

private static Single instance = null;

public static Single getInstance() {
if (instance == null) {
instance = new Single();
}
return instance;
}

}

但是你会发现这写法,只在单线程有效果,多线程就不适用了,毕竟如果两个线程都同时访问到if (instance == null) 的时候,就会出现2个实例咯。于是就再改改咯。

不好的解法(二):能在多线程中工作,但是效率不高

为了避免上面说的多线程不适用问题,那就加把锁吧~

public class Single {

private static Single instance = null;

public static Single getInstance() {
synchronized (Single.class) {
if (instance == null) {
instance = new Single();
}
}
return instance;
}

}

貌似确实解决了多线程访问的问题,但是呢?加一把同步锁,一个线程在访问的时候,另一个线程就停下来等待,好像是没问题,但是仔细想想,每次访问都加锁,这效率就低了,小时候,老师告诉我们,不要浪费。

可行的解法(三):加同步锁前后两次判断实例是否存在

于是为了弥补不足,便有了我们常见的经典写法(好像漏了点啥):

public class Single {

private static Single instance = null;

public static Single getInstance() {
if (instance == null) {
synchronized (Single.class) {
if (instance == null) {
instance = new Single();
}
}
}
return instance;
}
}

但是呢,因为CPU有时候点懒,可能把new Single()的结果放缓存了,值并未真正立马赋值给instance,这时候如果多线程访问,发现实际为null,又会重新创建,这样就不符合我们的单例的需求了。

可行的解法(四):

于是便有了我们网上最常见的写法,加上volatile以保证强制写回内存:

public class Single {

private static volatile Single instance = null;

public static Single getInstance() {
if (instance == null) {
synchronized (Single.class) {
if (instance == null) {
instance = new Single();
}
}
}
return instance;
}
}

这样在第一次的时候才会去加锁等待,刚刚好的弥补了上一种解法的缺点,但是这个方法也是有缺陷的,看着麻烦,写着也麻烦,而我在工作中发现,很多人偷懒,根本就懒得写,就直接写了第一种,然而这是非常不可取的。

Kotlin版:

class Singleton {
    companion object {
        @Volatile
        private var instance: Singleton? = null

        fun getInstance() = instance ?: synchronized(this) {
            instance ?: Singleton().also { instance = it }
        }
    }
}

或者

class Singleton {
    companion object{
        val instance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED){
            Singleton()
        }
    }
}

可行的解法(五):

我们都知道,静态的变量是最先初始化的,且唯一,那么,我们可以利用这个特性,写一个试试:

public class Single {

private static final Single instance = new Single();

private Single() {
}

public static Single getInstance() {
return instance;
}
}

这看着就挺好的了,简单,直接,BUT,这个类初始化得是不是有点太早了,这样我们不需要的时候就有了,是不是不太好。

强烈推荐的解法(六):利用静态内部类

既然知道了解法五的缺点,那我们来改进一下:

public class Single {

private Single() {
}

private static class SingleHolder {
private static final Single instance = new Single();
}

public static Single getInstance() {
return SingleHolder.instance;
}
}

这样我们在getInstance的时候,才会去初始化SingleHolder这个类,然后去初始化其静态成员变量,以达到“懒汉式”,用时才创建的思想。而没被改进的方案五,常被人称为“饿汉式”,即,用不着的时候就已经有了,急不可待。

Effective Java推荐写法

使用枚举进行单例,因为枚举类无构造函数,无法被反序列化。

public enum Singleton{
   INSTANCE;

   //do sth
}

解法比较:一二三四。。肯定是不推荐的,五,六就看大家各取所需啦,当然看完之后我相信大多数人会更喜欢第六种。

By the way:还有其他写法,比如说:解决序列化时不是单例(基于静态内部类)、利用静态代码块、利用枚举类、然后这些方法的演进,就是网上所说的最全单例了。

by lckiss

本站由以下主机服务商提供服务支持:

0条评论

发表评论