前言

本文主要内容

  • 介绍单例模式
  • 实现单例模式
  • 单例模式 VS 静态类

    正文

    介绍

单例模式(Singleton Pattern)是一个比较简单的模式,其定义如下: Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。) -设计模式之禅 第2版

简单的说就是:保证一个类在各种情景下,都只有一个实例化对象。

实现

饿汉模式

最简单的一种方式,对外界关闭创建与修改。使用 static 与 final 修饰既保证了一个类只有一个实例化变量,又保证了线程安全(静态常量在准备阶段完成了初始化)

  1. public class SingletonDemo {
  2. private static final SingletonDemo instance = new SingletonDemo();
  3. private SingletonDemo(){
  4. }
  5. public static SingletonDemo getInstance(){
  6. return instance;
  7. }
  8. }

这种方式实现简单,即时使用,但是创建就比较慢了。对创建一个类的引用变量来讲,影响极小,但是当创建多个类的引用类型变量时,就很明显了。可以换种方式,当使用时才进行实例化

懒汉模式

或者可以叫延迟加载?只有在获取实例时才进行实例化并返回对象。先看一个demo

Demo

  1. public class IdlerPattern {
  2. private IdlerPattern instance = null;
  3. private IdlerPattern(){
  4. }
  5. public static IdlerPattern getInstance(){
  6. if (instance == null) {
  7. instance = new IdlerPattern();
  8. }
  9. return instance;
  10. }
  11. }

代码如上,看起来实现了需求。但是在多线程环境下,以上代码可能会创建多个实例。比如,设某一时间有两个线程,分别为 t1 ,t2.

  • t1 调用 getInstance 方法,进行判断,instance == null 为true。运行到 instance = new IdlerPattern();
  • 此时,还没有执行 instance = new IdlerPattern(); 时间片使用完,进入等待队列
  • t2 调用 getInstance 方法,进行判断,instance == null 为true。运行到 instance = new IdlerPattern();创建实例成功,返回 instance。t2结束
  • t1 继续执行,创建实例,返回 instance

此时, 就有了两个实例。不满足需求

锁住整个方法

可以直接在方法中上锁,这样即使线程时间片使用完,另一个线程也无法进入该方法

  1. synchronized (IdlerPatternUseInternalLock.class){
  2. if(null == instance){
  3. instance = new IdlerPatternUseInternalLock();
  4. }
  5. }

但是,这样有个问题,对于读取时,因为并不会改变数据信息,因此多个线程一起访问也没有关系,但是这里的 synchronized 连读也一起锁定了,所以,可以进行优化下,减小 synchronized 的粒度

双重检查缩小粒度

通过判断 instance 是否为空,来确定是读还是写

  1. if(null == instance){
  2. synchronized (DoubleCheckLocking.class){
  3. if(null == instance){
  4. instance = new DoubleCheckLocking();
  5. }
  6. }
  7. }

在同步代码块中需要再判断一次,避免在进入时,刚好有线程实例化出一个对象。不过这种方式仍有问题,如果 cpu 为多核心,那么如果线程在不同核心上,可能 instance 信息同步不及时,也就是出现可见性问题。这里可以通过禁用缓存来解决。使用 volatile 来修饰 instance;

  1. package cn.zjm404.stu.dp.creat.singleton;
  2. public class DoubleCheckSingleton {
  3. private static volatile DoubleCheckSingleton instance = null;
  4. public static DoubleCheckSingleton getInstance(){
  5. if(instance == null){
  6. synchronized (DoubleCheckSingleton.class){
  7. if(instance == null){
  8. instance = new DoubleCheckSingleton();
  9. }
  10. }
  11. }
  12. return instance;
  13. }
  14. }

静态内部类

实现线程安全的延迟加载单例模式,除了用锁以外,还可以考虑使用下内部类的语言特性.内部类只有在被访问的时候才会被加载,而不是随着类的实例化而一同加载,因此,可以借用这个来实现延迟加载.让内部类使用饿汉模式即可

  1. public class StaticInnerClass {
  2. /**
  3. * 用作测试,统计实例数量
  4. */
  5. private static AtomicInteger num = new AtomicInteger(0);
  6. private StaticInnerClass(){
  7. System.out.println(num.incrementAndGet());
  8. };
  9. private static class InnerClass{
  10. final static StaticInnerClass INSTANCE = new StaticInnerClass();
  11. }
  12. public static StaticInnerClass getInstance(){
  13. return InnerClass.INSTANCE;
  14. }
  15. }

使用枚举类实现

  1. public enum EnumSingleton {
  2. INSTANCE;
  3. private EnumSingleton(){
  4. }
  5. }

单例模式 VS 静态类

通过上述描述,让一个类只有一个实现,那么完全可以不使用对象,而是使用静态类,那么什么使用时候单例,什么时候使用静态类呢?当考虑延迟加载时,或者要维护属性时,使用单例,除此之外,完全可以不使用对象,改用静态类

参考