天天看点

headfirst设计模式(6)—单例模式

单例入门浅析

HeadFirst的原文是由一个巧克力锅炉的例子引入了经典的单例模式,具体例子不赘述,直接进入经典单例模式的贴代码环节(注意:以下所有代码为了方便区分和源代码稍有不同)

经典的单例模式(线程不安全):

headfirst设计模式(6)—单例模式
public class ClassicSingleton {
    private static ClassicSingleton UNIQUE_INSTANCE;
 
    private ClassicSingleton() {
    }
 
    public static ClassicSingleton getInstance() {
        if (UNIQUE_INSTANCE == null) {
            UNIQUE_INSTANCE = new ClassicSingleton();
        }
        return UNIQUE_INSTANCE;
    }
 
    public String getDescription() {
        return "I'm a classic ClassicSingleton!";
    }
}      
headfirst设计模式(6)—单例模式

首先来说,为什么它是一个非常适合入门的单例?

因为它确实很简单,这段代码用一段话来描述就是:保证需要使用的对象在内存中的唯一性

个人觉得,这个就是单例的核心思想,后面各种单例模式都是为了这个操作在做各种各样的努力,只是实现的优劣之分而已。

再来聊聊为什么不推荐使用?

因为这个写法是一个线程不安全的,多线程下会有问题。所以这里用词是不推荐,万一你的用法就是单线程呢?这样写也没问题,so,只要能符合业务,通俗易懂,那么它就没有问题。老生常谈的问题,没有最好的,只有最适合的,简单高效才是硬道理,个人认为设计模式也只是为了辅助达成这个目标吧

记得我刚实习的时候,看网上的单例要用volatile, 要用synchronized,看得我那真是一愣一愣的,当时我synchronized,知道是干啥的,但是用得上,volatile,还要靠百度才知道是个啥。本着追求牛X技术的心情,直接就拷了一个个人感觉最牛X的volatile双重判断的写法上去。其实当时并不明白其中原理,只是觉得很牛X而已,幸好没出什么生产环境的bug,也是万幸。

不知道原理的代码是很恐怖的,因为这个东西有些可能是没有通过时间,业务检验的,即便是通过了测试,只要生产环境出问题,那就是毁灭性的(别问我怎么知道的)。技术是为了支撑业务,而不是为了炫技,写出简单,易用,高效的代码才是技术应该做的事情(当然并非是不鼓励尝试新技术,只是需要控制在一个可控的范围内)

打个比方,我写了一个处理权限的功能,其他人需要接进来的时候,我告诉他们,你们要去配一个xml文件,properties文件里面加两个参数,最后使用的时候,要调用xx方法,他们第一感觉就是,你写的这个太难用了。如果你告诉他们,把这个包引进去加个注解就可以了,其他的都不用管呢?是不是感觉完全不一样?

我擦,扯远了,总的来说就是它适合用于学习,不适合用于商业,那么有没有适合用于商业的呢?当然有,网上文章一大堆

第一种,简单粗暴的线程安全

headfirst设计模式(6)—单例模式
public class ThreadSafeSingleton {
    private static ThreadSafeSingleton UNIQUE_INSTANCE;

    private ThreadSafeSingleton() {
    }
 
    public static synchronized ThreadSafeSingleton getInstance() {
        if (UNIQUE_INSTANCE == null) {
            UNIQUE_INSTANCE = new ThreadSafeSingleton();
        }
        return UNIQUE_INSTANCE;
    }

    public String getDescription() {
        return "I'm a thread safe singleton!";
    }

}      
headfirst设计模式(6)—单例模式

效率很低,但是能用,依然是不推荐的类型,有的朋友可能要问了,那不推荐你写出来干啥?

它还是有优势的,它理解起来真的很简单,同时编程也不复杂,这个不就是我们一直追寻的东西吗?如果一个问题没有更好的解决方案,那么理解简单,编程简单的方案也不失为一个方案吧?至少能看懂啊

当然单例模式这里确实是不推荐的,因为我知道的还有至少3种比它好,所以不推荐

第二种,使用静态初始化变量

headfirst设计模式(6)—单例模式
public class StaticallyInitializedSingleton {
    private static StaticallyInitializedSingleton UNIQUE_INSTANCE = new StaticallyInitializedSingleton();
 
    private StaticallyInitializedSingleton() {}
 
    public static StaticallyInitializedSingleton getInstance() {
        return UNIQUE_INSTANCE;
    }
    
    public String getDescription() {
        return "I'm a statically initialized ClassicSingleton!";
    }
}      
headfirst设计模式(6)—单例模式

这种算是最推荐的写法了,首先它写法简单,其次线程安全问题可以通过jvm去保证(每个类的静态变量在jvm中只会被初始化一次),最后,获取单例类的操作没有加锁处理,性能很高

但是,它也不是没有问题,一般来说,需要单例的类都比较耗性能,创建了不用还是比较伤的(当然,有的时候,有钱能解决很多这样的问题),还有一种可能是如果在初始化类的时候,创建单例类失败了,那这个类里面所有的方法都没法用了,如果在spring的环境下,再有一个@Component之类的注解,或者能够被spring扫描到的其他操作那就更好玩了,可能项目都启动不起来。

这个东西就要看这个单例对于项目是不是强依赖了,仁者见仁智者见智了,此处就不赘述,不然又要跑偏了

第三种,双重加锁检查

headfirst设计模式(6)—单例模式
public class DoubleCheckSingleton {
    private volatile static DoubleCheckSingleton UNIQUE_INSTANCE;
 
    private DoubleCheckSingleton() {}
 
    public static DoubleCheckSingleton getInstance() {
        if (UNIQUE_INSTANCE == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (UNIQUE_INSTANCE == null) {
                    UNIQUE_INSTANCE = new DoubleCheckSingleton();
                }
            }
        }
        return UNIQUE_INSTANCE;
    }
    public String getDescription() {
        return "I'm a double check singleton!";
    }
}      
headfirst设计模式(6)—单例模式

这个就是个人觉得一看就是很牛X的那种写法,当然,它的各方面也都是相当优秀的,线程安全,容错,性能都不错

但是,如果对它的理解比较模糊的话,那么写的时候是很容易写掉一些重点的东西的,举两个点:

1,静态变量里面的volatile容易写掉吧?

2,synchronized里面那个判空容易写掉吧?

对于2这点,我是深有体会的,如果A,B线程同时调用getInstance()方法,假设UNIQUE_INSTANCE还没有初始化,同时A线程先进入synchronized块,没有if null判断,那么它就new了一个对象出来吧,当A执行完了以后释放了锁,这个时候B就会进入,没有if null判断,B也new一个对象出来,这就有问题了啊。

对于1这点,如果不理解volatile,是很容易写掉的,毕竟,如果能很好的理解第2点的话,就会感觉,不加这啥volatile感觉也没啥问题啊,双重锁稳的不行啊。但是不加还真有可能有问题