1. 懒汉式(非线程安全)
    1. public class SingleTonDemo {
    2. private static SingleTonDemo instance;
    3. private SingleTonDemo() {}
    4. public static SingleTonDemo getInstance() {
    5. if(instance == null) {
    6. instance = new SingleTonDemo();
    7. }
    8. return instance;
    9. }
    10. }

    这是一个很常见的单例模式,创建了一个类叫SingleTonDemo,为什么这样就能够实现单例?
    (1)声明了一个私有静态的变量instance
    (2)将不带参的构造函数私有化,可以防止在其他类中可以new这个类。
    (3)提供一个公共获取实例的静态构造方法。为什么是静态的?因为该方法是给外部在没有实例这个类的时候可以调用
    (4)判断instance是否为空,为空才实例

    在不考虑并发的情况下,这种构造方式就足够了。那如果要考虑并发呢?

    1. 懒汉式(线程安全)
    1. public class SingleTonDemo {
    2. private static SingleTonDemo instance;
    3. private SingleTonDemo() {}
    4. public static synchronized SingleTonDemo getInstance() {
    5. if(instance == null) {
    6. instance = new SingleTonDemo();
    7. }
    8. return instance;
    9. }
    10. }

    在并发的情况下,多个线程同时调用getInstance的时候,如果是第一种单例构造方式,会出现多个线程判断到instance==null,然后就会创建多个实例了,这就不是创建唯一实例了。所以当前这种构造方式就是在调用getInstance的时候加了个synchronized,将整个获取实例的方法同步。这种构造方式有弊端,当一个线程访问的时候,其他线程就会被挂起,虽然可以保证了单实例的安全性,但是也造成了很多无谓的等待

    1. 双检锁/双重校验锁
    1. public class SingleTonDemo {
    2. private static SingleTonDemo instance;
    3. private SingleTonDemo() {}
    4. public static SingleTonDemo getInstance() {
    5. if(instance == null) {
    6. synchronized(SingleTonDemo.class) {
    7. if(instance == null) {
    8. instance = new SingleTonDemo();
    9. }
    10. }
    11. }
    12. return instance;
    13. }
    14. }

    那其实不需要每次调用getInstance都加个同步锁,只需要在生成实例的时候加上同步锁。那为什么还需要第二次判断instance是否为空呢?
    假设在没有实例的情况下,a线程获取了同步锁,生成了实例。此时b线程也进来了,由于没有二次判空,还是创建一个新的实例。那是不是这种单例构造方式就最好呢?又解决了线程安全的问题,又提升了性能。其实也未必,看网上说到了jvm层面创建实例时候,有可能会造成莫名的错误。

    1. 饿汉式
    1. public class SingleTonDemo {
    2. private static SingleTonDemo instance = new SingleTonDemo();
    3. private SingleTonDemo() {}
    4. public static SingleTonDemo getInstance() {
    5. return instance;
    6. }
    7. }

    这种构造方式,在类加载的时候就实例化了。我看公司项目基本上都用这种,虽然不是懒加载,可能会内存的浪费,但是感觉比较粗暴,也不容易出错。

    1. 静态内部类
    1. public class SingleTonDemo {
    2. private SingleTonDemo() {}
    3. private static class SingleInner{
    4. private static SingleTonDemo instance = new SingleTonDemo();
    5. }
    6. public static SingleTonDemo getInstance() {
    7. return SingleInner.instance;
    8. }
    9. }

    这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 4 种方式不同的是:第 4 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 4 种方式就显得很合理。

    上面这段话其实是引用菜鸟教程的,里面还介绍了一种枚举模式。

    单例模式在我印象中只有1、2、4,其实就是懒汉式跟饿汉式,现在又多了解了双检锁跟静态内部类两种方式,也算有点收获。

    文章不足之处,还真希望有读者评论一下,才能写得更好,谢谢大家。