理解单例模式?看这篇就够了!

简介

在日常开发中,单例模式是我们最常用的设计模式之一,即便你是一个编程小白,对设计模式一窍不通,在开发中也会经常见到或者使用到这个模式。它的应用十分广泛,这一切都源于它的存在意义,即:

确保某一个类只有一个实例,且自行实例化并向整个系统提供这个实例

使用场景

顾名思义,当我们的系统中只需要且只能存在该类的一个实例时,单例模式就登场了!!

  • 线程池
  • 缓存
  • ImageLoader
  • 访问数据库
  • 单用户系统的用户

等等
无论是为了避免产生多个对象带来的资源消耗问题,还是系统本身就只能存在该类的一个对象,这时候单例模式都是当仁不让的最佳模式。
有人可能会反驳,我们使用静态变量或者方法也可以实现类似的功能啊,确实,这种方法很多情况下也可以实现,但是单例模式还有个不得不说的优点,即它可以只在你用到它的时候它才初始化该实例,保证不会做无谓的初始化,这是静态所达不到的。

关键点

因为单例模式的特殊性,只能存在一个实例,所以为了避免用户实例化该实例,我们要把构造函数设为私有,而通过一个静态方法或者枚举返回该实例,同时还有至关重要的一点,就是要考虑多线程下单例的安全性,即保证在多线程情况下,单例还是单例,不会因线程不安全问题创建出多个来。

  • 构造函数私有
  • 提供获取该实例的静态方法(或枚举)
  • 避免在多线程情况下的重复创建

Show me the code

1. 饿汉式

public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {
    }

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

我们把构造函数私有化,并且提供静态的getInstance方法获取instance实例,实现了所说的饿汉式单例,之所以叫它饿汉,是因为它在类加载的时候便初始化了该实例,而不是在需要的时候初始化。
优点:简单,线程安全
缺点:由于不是按需初始化,容易造成一定程度的资源浪费。

2.懒汉式

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

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

懒汉式,一听名字就明白,它是为了解决懒汉式不能按需初始化的问题来的。它把初始化放到了getInstance方法中,每次做判断保证了只有当第一次使用时才进行初始化,同时给getInstance方法加了synchronized关键字以保证线程安全。
怎么样,到这里懒汉式貌似是非常完美的一种单例实现方案了,但是每次调用getInstance都通过synchronized方式加锁又会造成性能损失(见知识扩展),所以它也是有很大缺点的。
优点:只有在使用时才被初始化
缺点:首先,造成了不必要的同步开销;其次,由于第一次加载时需要及时实例化,如果对象初始化复杂,会反应稍慢;

知识扩展
Java的线程是映射到操作系统的原生线程之上的,如果要阻塞或者唤醒一个线程,都需要操作系统来帮忙完成,这就需要从用户态转换到核心态当中,因此状态转换需要耗费很多的处理器时间。对于代码简单的同步块,状态转换消耗的时间可能比用户代码执行的时间还要长。所以,synchronized是Java语言中一个重量级的操作,有经验的程序员都会在确实必要的情况下才使用这种操作。 —— 摘自《深入理解Java虚拟机》

3.双重检查式(DCL)

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

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

为了避免不必要的同步,我们又进行了升级,在getInstance方法中,因为我们只有在未被初始化时进行同步,所以加了外层的判空,这下终于解决了同步浪费问题。那为什么同步代码块中还要判空呢?之所以还要再判断下,这是因为加锁和实例化都需要一定的时间,如果在这期间又有一个线程进入了getInstance方法,此时实例化过程还未完成,这个线程自然就通过了第一层判断,然后等待后也进入了同步代码块,如果不再做判空,就会造成重复实例化。
又有同学说了,为什么给instance对象加了个volatile关键字,这个volatile又是什么鬼?我们知道,当程序执行instance = new Singleton();这段代码时,虽然看着是一句代码,但它并不是一个原子操作,这句代码最终会编译成多条汇编指令:

  1. 给Singleton的实例分配内存;
  2. 调用Singleton()的构造函数,初始化成员字段
  3. 将sInstance对象指向分配的内存空间(此时instance才不是null了)

由于Java虚拟机允许对指令进行重排序操作(以优化执行性能),上面的2,3的顺序是无法保证的,这时就会产生DCL失效问题,也就是判空失效,从而造成重复实例化。这时,volatile关键字就来解决这个问题了,JVM在JDK1.5具体化了volatile关键字,被volatile关键字修饰的变量,每次读取变量值都是从主内存中读取,禁止了指令重排序,从而保证了DCL实现单例的安全性。
这种方式能满足我们安卓开发中的大部分实现单例的需求,即实现了懒加载,又保证了线程安全。看吧,我们Android程序员常用的EventBus框架就是基于DCL实现单例的:

static volatile EventBus defaultInstance;
      public static EventBus getDefault() {
          if (defaultInstance == null) {
              synchronized (EventBus.class) {
                  if (defaultInstance == null) {
                      defaultInstance = new EventBus();
                  }
              }
          }
          return defaultInstance;
      }

4.枚举方式

public enum Singleton {
    INSTANCE
}

枚举方式是最简单的一种单例实现方式,要说缺点嘛,我没发现呢:)

5.静态内部类方式

public class Singleton {
    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
    
    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();
    }
}

推荐!推荐!推荐!
重要的事情说三遍。
当第一次加载Singleton类时并不会初始化instance,只有在第一次调用Singleton的getInstance方法时才会导致instance被实例化。因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能保证线程安全,也能保证单例对象的唯一性,同时也延迟了单例的实例化,所以推荐使用。

后记

在Android开发过程中,从系统的LayoutInflater到各种开源框架如Universal-ImageLoader,还有我们开发人员为了实现更优化的系统自己编写的,单例模式真是无处不在。单例有多种实现方式,我们可以按需要选择最适合自己的一种,同时在使用单例的时候不要大意哦,例如它可能会造成内存泄露等问题,或者反序列化时单例失效的问题,这都是我们在使用单例的时候需要注意的点!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值