设计模式篇之单例模式(一)

101 阅读3分钟

单例模式

引言

本文主要探讨单例模式的几种实现方式及优缺点

  • 饿汉式
  • 静态内部类式
  • 懒汉式
  • 线程安全的懒汉式
  • 双重校验锁式

instance.png

详细介绍

饿汉式

  • 基本不会使用,程序启动或类被加载时,对象就会实例化
  • 浪费资源
object InstanceDemo

// 查看字节码:Tools -> Kotlin -> Show Kotlin Bytecode
// class version 52.0 (52)
// access flags 0x31
public final class com/dcxing/InstanceDemo {
  // access flags 0x2
  private <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/dcxing/InstanceDemo; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x19
  public final static Lcom/dcxing/InstanceDemo; INSTANCE
  @Lorg/jetbrains/annotations/NotNull;() // invisible

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 3 L0
    NEW com/dcxing/InstanceDemo
    DUP
    INVOKESPECIAL com/dcxing/InstanceDemo.<init> ()V
    ASTORE 0
    ALOAD 0
    PUTSTATIC com/dcxing/InstanceDemo.INSTANCE : Lcom/dcxing/InstanceDemo;
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 1

  @Lkotlin/Metadata;(mv={1, 6, 0}, k=1, d1={"\u0000\u000c\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0008\u0002\u0008\u00c6\u0002\u0018\u00002\u00020\u0001B\u0007\u0008\u0002\u00a2\u0006\u0002\u0010\u0002\u00a8\u0006\u0003"}, d2={"Lcom/dcxing/InstanceDemo;", "", "()V", "android_testRelease"})
  // compiled from: InstanceDemo.kt
}

// 通过Decompile,可以看到和java声明方式一样
@Metadata(
   mv = {1, 6, 0},
   k = 1,
   d1 = {"\u0000\f\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\bÆ\u0002\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002¨\u0006\u0003"},
   d2 = {"Lcom/dcxing/InstanceDemo;", "", "()V", "android_testRelease"}
)
public final class InstanceDemo {
   @NotNull
   public static final InstanceDemo INSTANCE;

   private InstanceDemo() {
   }

   static {
      InstanceDemo var0 = new InstanceDemo();
      INSTANCE = var0;
   }
}

静态内部类式

  • 既可以做到延迟加载,又不必使用同步关键字
  • 线程安全(java中class加载互斥)
  • 减少内存消耗
  • 单例中存在多线程环境,内部类可避免该问题
  • jvm对类初始化做了限制,同一时间只允许一个线程去初始化一个类,从虚拟机层面避免大部分单例实现问题
class InstanceDemo private constructor() {
    companion object {
        val instance = InstanceHolder.holder
    }

    private object InstanceHolder {
        val holder = InstanceDemo()
    }
}

懒汉式

  • 延迟加载,按需加载(第一次访问时创建)
  • 线程不安全,多线程易出现不同步情况
class InstanceDemo private constructor() {
    companion object {
        private var instance: InstanceDemo? = null
            get() {
                if (field == null) {
                    field = InstanceDemo()
                }
                return field
            }

        fun get(): InstanceDemo {
            // 此处不用getInstance作为方法名是因为伴生对象声明时,内部已有getInstance方法,所以要用其他名字
            return instance!!
        }
    }
}

线程安全的懒汉式

  • 再懒汉式基础上增加同步锁:@Synchronized
  • 解决线程不安全问题
  • 效率低,每次访问都要判断同步锁
  • Android中InputManager、AccessibilityManager等使用此方式实例化
class InstanceDemo private constructor() {
    companion object {
        private var instance: InstanceDemo? = null
            get() {
                if (field == null) {
                    field = InstanceDemo()
                }
                return fieled
            }

        @Synchronized
        fun get(): InstanceDemo {
            // 此处不用getInstance作为方法名是因为伴生对象声明时,内部已有getInstance方法,所以要用其他名字
            return instance!!
        }
    }
}

双重校验锁式

  • 并发量不多,安全性不高的情况下可正常运行,但在jvm编译过程中会出现指令重排优化过程,会导致其实际未被初始化就分配内存空间,导致其不完整(www.360doc.com/content/11/…)
  • 大部分项目使用此方式创建
// 无参
class InstanceDemo private constructor() {
    companion object {
        val instance: InstanceDemo by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { InstanceDemo() }
    }
}

// 有参
class InstanceDemo private constructor(private val context: Context) {
    companion object {
        @SuppressLint("StaticFieldLeak")
        @Volatile private var instance: InstanceDemo? = null
        fun getInstance(context: Context) =
            instance ?: synchronized(this) {
                instance ?:InstanceDemo(context).also { instance = it }
            }
    }
}