最近在寫項(xiàng)目的同時(shí)也用到了單例模式工窍,kotlin的單例還不是很會寫拾弃,現(xiàn)在就總結(jié)下java寫法對應(yīng)的kotlin是如何寫的帜讲。
- 餓漢式
- 懶漢式
- 線程安全的懶漢式
- 雙重校驗(yàn)鎖式
- 靜態(tài)內(nèi)部類式
單例模式的基本思想就是在程序運(yùn)行過程中不會重復(fù)創(chuàng)建要使用的對象霍骄,有且只創(chuàng)建一次群嗤。這就需要用到kotlin中的object和companion object(伴生對象),因?yàn)樗麄兛梢猿洚?dāng)java下的static氏淑。
餓漢式實(shí)現(xiàn)
- java
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
- kotlin
object Singleton
可以看到kotlin的餓漢單例實(shí)現(xiàn)只有一行代碼勃蜘。用到了object關(guān)鍵字,object關(guān)鍵字定義一個(gè)類并同時(shí)創(chuàng)建一個(gè)實(shí)例(就是一個(gè)對象)夸政。它其中的一種使用場景就是對象聲明定義單例元旬。
與類一樣榴徐,一個(gè)對象聲明也可以包含屬性守问、方法、初始化語句塊等的聲明坑资。唯一不允許的就是構(gòu)造方法(包括主構(gòu)造方法和從構(gòu)造方法)耗帕。與普通類的實(shí)例不同,對象聲明在定義的時(shí)候就立即創(chuàng)建了袱贮,不需要在代碼的其他地方調(diào)用構(gòu)造方法仿便。為對象聲明定義一個(gè)構(gòu)造方法是沒有意義的。
與變量一樣攒巍,對象聲明允許使用對象名.字符的方法來調(diào)用方法和訪問屬性嗽仪。
下面是kotlin餓漢式編譯成java代碼后的代碼,幫助我們理解柒莉。
public final class Singleton {
public static final Singleton INSTANCE;
static {
Singleton var0 = new Singleton();
INSTANCE = var0;
}
}
可以看到編譯后的Java代碼闻坚,直接將初始化對象的代碼放在了靜態(tài)代碼塊中。
餓漢式實(shí)現(xiàn)
- java
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
- kotlin
class Singleton private constructor(){
companion object{
private var instance:Singleton? = null
get(){
if(field == null){
field = Singleton()
}
return field
}
fun get():Singleton{
return instance!!//!!表示當(dāng)前對象不為空的情況下執(zhí)行
}
}
}
這里使用了伴生對象兢孝,在其內(nèi)部創(chuàng)建對象并調(diào)用get()方法返回窿凤。
看看kotlin代碼編譯成java代碼的樣子:
public final class Singleton {
private static Singleton instance;
public static final Singleton.Companion Companion = new Singleton.Companion((DefaultConstructorMarker)null);
private Singleton() {
}
// $FF: synthetic method
public Singleton(DefaultConstructorMarker $constructor_marker) {
this();
}
public static final class Companion {
//創(chuàng)建單例對象
private final Singleton getInstance() {
if (Singleton.instance == null) {
Singleton.instance = new Singleton((DefaultConstructorMarker)null);
}
return Singleton.instance;
}
private final void setInstance(Singleton var1) {
Singleton.instance = var1;
}
@NotNull
public final Singleton get() {
Singleton var10000 = ((Singleton.Companion)this).getInstance();
if (var10000 == null) {
Intrinsics.throwNpe();
}
return var10000;
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
線程安全的懶漢式實(shí)現(xiàn)
- java
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){//使用同步鎖
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
- kotlin
class Singleton private constructor(){
companion object{
private var instance: Singleton? = null
get(){
if(field == null){
field = Singleton()
}
return field
}
@Synchronized
fun get(): Singleton{
return instance!!
}
}
}
在kotlin中如果需要將方法聲明為同步,需要添加 @Synchronized注解跨蟹。
康康編譯為java代碼的樣子:
public final class Singleton {
private static Singleton instance;
public static final Singleton.Companion Companion = new Singleton.Companion((DefaultConstructorMarker)null);
private Singleton() {
}
// $FF: synthetic method
public Singleton(DefaultConstructorMarker $constructor_marker) {
this();
}
public static final class Companion {
private final Singleton getInstance() {
if (Singleton.instance == null) {
Singleton.instance = new Singleton((DefaultConstructorMarker)null);
}
return Singleton.instance;
}
private final void setInstance(Singleton var1) {
Singleton.instance = var1;
}
//get()方法 使用了同步鎖
@NotNull
public final synchronized Singleton get() {
Singleton var10000 = ((Singleton.Companion)this).getInstance();
if (var10000 == null) {
Intrinsics.throwNpe();
}
return var10000;
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
雙重校驗(yàn)鎖式實(shí)現(xiàn)
- java
public class Singleton{
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
雙重鎖模式應(yīng)該是日常開發(fā)中比較常用的一種形式了雳殊,現(xiàn)在復(fù)習(xí)下其中的知識點(diǎn)。
- 第一次判斷singleton是否為null
第一次判斷是在synchronized同步代碼塊外進(jìn)行判斷窗轩,由于單例模式只會創(chuàng)建一個(gè)實(shí)例夯秃,并通過getInstance方法返回singleton對象,所以第一次判斷是為了在singleton對象已經(jīng)創(chuàng)建的情況下,避免進(jìn)入同步代碼塊仓洼,提升效率箫措。
- 第二次判斷singleton是否為null
第二次是為了避免以下情況的發(fā)生。
(1)假設(shè):線程A已經(jīng)經(jīng)過第一次判斷衬潦,判斷singleton=null,準(zhǔn)備進(jìn)入同步代碼塊斤蔓。
(2)此時(shí),線程B獲得時(shí)間片镀岛,由于線程A并沒有創(chuàng)建實(shí)例弦牡,所以判斷singleton仍為null,所以線程B創(chuàng)建了實(shí)例singleton。
(3)此時(shí)漂羊,線程A再次獲得時(shí)間片驾锰,由于剛經(jīng)過第一次判斷singleton為null(不會重復(fù)判斷),進(jìn)入同步代碼快走越,這個(gè)時(shí)候如果不加入第二次判斷的話椭豫,線程A又會創(chuàng)建一個(gè)實(shí)例singleton,就不滿足單例模式的需求,所以第二次判斷是很有必要的旨指。
- 加volatile關(guān)鍵字的原因
第一赏酥,volatile可以保證可見性和原子性,同時(shí)保證JVM對指令不會進(jìn)行重排列谆构。
第二裸扶,對象的創(chuàng)建不是一步完成的,是一個(gè)符合操作搬素,需要三個(gè)指令呵晨。
singleton = new Singleton() 為例子
指令1:獲取singleton對象的內(nèi)存地址
指令2:初始化singleton對象
指令3:將這塊內(nèi)存地址指向引用變量singleton
由于volatile禁止JVM對指令進(jìn)行重排序,所以創(chuàng)建對象的過程仍然或按照指令1-2-3有序的執(zhí)行熬尺。
若沒有volatile關(guān)鍵字摸屠,在多線程的情況下,假設(shè)線程A正常創(chuàng)建一個(gè)實(shí)例粱哼,那么指定執(zhí)行的順序可能2-1-3季二,當(dāng)執(zhí)行到指令1的時(shí)候,線程B執(zhí)行g(shù)etInstance方法皂吮,獲取到的戒傻,可能是對象的一部分,或者是不正確的對象蜂筹,程序可能就會報(bào)異常信息需纳。
- kotlin
class SingletonDemo private constructor() {
companion object {
val instance: SingletonDemo by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
SingletonDemo() }
}
}
看到這種寫法,哇艺挪,真的比java少了很多代碼不翩,但是越簡單的代碼就會包含越多的信息點(diǎn)兵扬,我們需要理解為什么要這樣做。這里用到了by lazy(),kotlin的委托屬性的延遲屬性口蝠。
延遲屬性:其值只在首次訪問時(shí)計(jì)算器钟。委托屬性具體可參考官方文檔。
lazy函數(shù)返回一個(gè)對象妙蔗,該對象具有一個(gè)名為getValue且簽名正確的方法傲霸,因此可以把它與by關(guān)鍵字一起使用來創(chuàng)建一個(gè)委托屬性。默認(rèn)情況下眉反,lazy函數(shù)是線程安全的昙啄,如果需要可以設(shè)置其它選項(xiàng)來高速它要使用哪個(gè)鎖,或者完全避開同步寸五,如果該類永遠(yuǎn)不會在多線程環(huán)境中使用梳凛。
下面來看看源碼加深理解:
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)
}
...
}
進(jìn)入了SynchronizedLazyImpl():
internal object UNINITIALIZED_VALUE
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
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
//判斷是否已經(jīng)初始化過,如果初始化過直接返回梳杏,不在調(diào)用高級函數(shù)內(nèi)部邏輯
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!!()//調(diào)用高級函數(shù)獲取其返回值
_value = typedValue//將返回值賦值給_value,用于下次判斷時(shí)韧拒,直接返回高級函數(shù)的返回值
initializer = null
typedValue
}
}
}
//省略部分代碼
}
靜態(tài)內(nèi)部類式實(shí)現(xiàn)
- java
public class Singleton{
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
- kotlin
class Singleton private constructor(){
companion object{
val instance = SingletonHolder.holder
}
private object SingletonHolder{
val holder = Singleton()
}
}
注意
對象聲明(object)不是一個(gè)表達(dá)式,不能用在賦值語句的右邊十性。
對象聲明的初始化是線程安全的并且在首次訪問時(shí)進(jìn)行叛溢。
對象聲明不能在局部作用域(即不能直接嵌套在函數(shù)內(nèi)部),但是他們可以嵌套到其他對象聲明或非內(nèi)部類中烁试。