单例模式
一个类有且仅有一个实例,并且自行实例化向整个系统提供.保证全局只有一个实例对象
饿汉式 - 在使用之前就创建好了
使用kotlin的关键字 object 就可以 - static类块里初始化,只加载一次
1 2 3 4 5 6 7
| object Singleton{ val bean = 1 fun log():Int{ val beans = bean + 1 return beans } }
|
Android Studio -> Tools -> Kotlin -> show Kotlin bytecode -> Decompile
反编译后看原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public final class Singleton { private static final int bean; @NotNull public static final Singleton INSTANCE;
public final int getBean() { return bean; }
public final int log() { int beans = bean + 1; return beans; }
private Singleton() { }
static { Singleton var0 = new Singleton(); INSTANCE = var0; bean = 1; } }
|
懒汉式 - 在使用时才创建
线程不安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Singleton private constructor(){ companion object{ private var singleton : Singleton? = null get() { if (field == null) field = Singleton() return field; } fun get() : Singleton? { return singleton } } fun log(){ BookLogger.i("Singleton") } }
|
反编译为java为: 如果在单例对象创建之前,同时有两个线程要使用对象,那判断对象为null,就有可能同时创建了两个对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public final class Singleton { private static Singleton singleton;
@NotNull public static final Singleton.Companion Companion = new Singleton.Companion((DefaultConstructorMarker)null); private Singleton() { }
public Singleton(DefaultConstructorMarker $constructor\_marker) { this(); }
public static final class Companion { private final Singleton getSingleton() { if (Singleton.singleton == null) { Singleton.singleton = new Singleton((DefaultConstructorMarker)null); }
return Singleton.singleton; }
private final void setSingleton(Singleton var1) { Singleton.singleton = var1; }
@Nullable public final Singleton get() { return (([Singleton.Companion)this).getSingleton(); }
private Companion() { }
public Companion(DefaultConstructorMarker $constructor\_marker) { this(); } } }
|
线程安全 - 方案一: 直接给get方法加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Singleton private constructor(){ companion object { private var singleton : Singleton? = null get() { if (field == null) field = Singleton() return field; } @Synchronized fun get() : Singleton? { return singleton } } }
|
分析:只给get()方法加锁,若在构造方法里对对象进行「a++」 类似操作,可能在某个线程只创建对象,分配了内存,还没完成数据操作,另一个线程就拿到了对象「a」,可能会出现数据错误,可以使用volatile关键字限制代码指令重排解决;并且对get()方法加锁,每个线程需要拿到锁,才能对对象进行操作,比较耗时.
线程安全 - 方案二:DCL (double check lock)
方案一每次调用该方法的时候都得获取锁,但是如果这个单例已经被初始化了,其实按道理就不需要申请同步锁了,直接返回这个单例类实例即可,使用kotlin的by lazy函数,指定SYNCHRONIZED线程安全模式,这种方式可以保证「在第一次使用这个属性时执行初始化代码,并保存属性,后续访问的时候直接返回,不再执行初始化代码块」.
1 2 3 4 5 6 7
| class Singleton private constructor(){ companion object { val Instance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { Singleton() } } }
|
反编译成java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public final class Singleton { @NotNull private static final Lazy Instance$delegate; @NotNull public static final Singleton.Companion Companion = new Singleton.Companion((DefaultConstructorMarker)null);
private Singleton() { }
static { Instance$delegate = LazyKt.lazy(LazyThreadSafetyMode.SYNCHRONIZED, (Function0)null.INSTANCE); }
public Singleton(DefaultConstructorMarker $constructor_marker) { this(); }
public static final class Companion { @NotNull public final Singleton getInstance() { Lazy var1 = Singleton.Instance$delegate; Singleton.Companion var2 = Singleton.Companion; Object var3 = null; return (Singleton)var1.getValue(); }
private Companion() { }
public Companion(DefaultConstructorMarker $constructor_marker) { this(); } } }
|
在类块中初始化,使用 kotlin 的函数 LazyKt.lazy,设置 LazyThreadSafetyMode. SYNCHRONIZED 模式,LazyKt.lazy 点进去的函数为 ->
1 2 3 4 5 6 7
| public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = when (mode) { LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) }
|
根据mode参数,下一步之行SynchronizedLazyImpl(initializer),继续进入 ->
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable { private var initializer: (() -> T)? = initializer @Volatile private var _value: Any? = UNINITIALIZED_VALUE private val lock = lock ?: this
override val value: T get() { val _v1 = _value if (_v1 !== UNINITIALIZED_VALUE) { @Suppress("UNCHECKED_CAST") return _v1 as T }
return synchronized(lock) { val _v2 = _value if (_v2 !== UNINITIALIZED_VALUE) { @Suppress("UNCHECKED_CAST") (_v2 as T) } else { val typedValue = initializer!!() _value = typedValue initializer = null typedValue } } }
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value) }
|
梳理上段代码可见,在 get 对象时,先判断_v1是否存在, !== 判断对象指向内存地址是否存在,直接判断是否是同一个对象,而不是对象值是否一致,如果对象没有内存地址,则对 lock 对象加锁进行初始化,同时 _value 声明为 Volatile 类型,保证多线程数据一致性.
补充volatile关键字原理
volatile关键字是Java中的一个类型修饰符,通过禁止编译器、CPU指令重排序和部分happens-before 规则,解决了有序性问题;通过缓存一致性协议保证每个线程获得最新值,保证每个线程拿到的参数为最新值.
静态内部类去创建单例
1 2 3 4 5 6 7 8 9 10 11 12
| class Singleton private constructor(){ companion object { fun getInstance()= SingletonHolder.ins }
private object SingletonHolder{ var ins = Singleton() } fun logOO():String{ return "OO" } }
|
枚举实现
1 2 3 4 5 6 7 8
| enum class Singleton { INSTANCE;
fun doSomeThing() { println("do some thing") } }
|
反编译成java代码:
1 2 3 4 5 6 7 8 9 10 11 12
| public final class EnumSingleton extends Enum<EnumSingleton> { public static final EnumSingleton ENUMSINGLETON; private static final Singleton ENUM$VALUES\[];
public static EnumSingleton[] values (); public static EnumSingleton valueOf (String s); static { ENUM$VALUES = (new EnumSingleton[] { ENUMSINGLETON }); }; }
|
通过反编译后,可以看到单例枚举类是一个final类,且创建该对象的实例是在一个static静态语句块中进行的,根据JVM的类加载机制,静态语句块只会在类被加载时执行一次,所以可以线程安全。另外因为单例枚举类反编译后实际上是一个被final修饰的类,所以他不能被继承,也就不能创建子类对象。
1、采用反射方式来创建:
1 2 3 4 5 6
| public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { MediaTypeEnum mp3 = MediaTypeEnum.MP3; MediaTypeEnum instance = mp3.getClass().getDeclaredConstructor(String.class, int.class).newInstance(); System.out.println(instance); }
|
会报错:
我们在此处的报错信息打开,发现newInstance()方法直接不让enum类型利用反射来创建实例。所以利用反射方式来破坏单例模式不可能。
2、实现java.lang.Cloneable接口,调用clone方法来创建:
MediaTypeEnum mp3 = MediaTypeEnum.MP3;
// 克隆方式
Object clone = mp3.clone();
仍会报错
我们调用enum枚举的时候实际上调用的是父类:java.lang.Enum里面的clone()。但是Enum的clone()也是直接抛出异常,如下。所以clone()方式也是不行的。
3、采用实现java.io.Serializable接口,反序列化方式:
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象,不存在创建对象的可能。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,所以此方式也行不通。
综上,enum不允许通过反射创建实例,不允许通过clone()创建,也不允许通过反序列化的方式创建对象,所以可以避免反射攻击.
对比:
类型 |
优点 |
缺点 |
饿汉式 |
在类加载时就初始化,保证只进行了一次初始化,线程安全,实现简单 |
在类加载时就初始化,会存在资源浪费 |
懒汉式 |
方法调用时才创建对象,节省资源 |
线程不安全,多线程情况下存在创建多个实例的风险 |
懒汉式 -直接加锁 |
线程安全 |
每个线程获取对象都需要先获得锁,再执行方法,耗时 |
懒汉式(推荐) -DCL |
节省内存空间,并且线程安全 |
需要1-2次是否初始化的判断 |
静态内部类(推荐) |
节省内存空间,并且线程安全 |
创建了一个新的类去调用 |
枚举 |
节省内存空间,并且线程安全,可以避免反射攻击 |
在类加载时就初始化,会存在资源浪费 |