1. 程式人生 > >Android中的單例模式(包含Java、Kotlin)

Android中的單例模式(包含Java、Kotlin)

  在Android開發工程中,單例模式可以說是我們使用得非常頻繁的設計模式了。常見的寫法有5種:

  1. 餓漢式
  2. 懶漢式
  3. 同步鎖
  4. 雙重校驗
  5. 內部類

下面我們對這5種寫法的Java、Kotlin各自舉例。呼叫統一由Kotlin呼叫(其實差別並不大)

一、餓漢式

java:

public class BaseSingleton {
    private static final String TAG = BaseSingleton.class.getSimpleName();
    private BaseSingleton(){
        Log.e(TAG,"BaseSingleton init");
    }
    private static BaseSingleton INSTANCE = new BaseSingleton();
    public static BaseSingleton getInstance(){
        return INSTANCE;
    }
    public void sayHello(String name){
        Log.e(TAG,"Hello "+name+",nice to meet you !");
    }
}

Kotlin:

object KBaseSingleton {
    @JvmStatic
    fun sayHello(name:String){
        Log.e(this::class.java.simpleName,"Hell $name,nice to meet you !")
    }
}

是不是覺得Kotlin特別的簡單?是的,Kotlin一個Object就可以標記一個單例了。

呼叫:

BaseSingleton.getInstance().sayHello("Calm")
KBaseSingleton.sayHello("Calm")

可以看到這種寫法很簡單,但靜態變數的程式碼在類載入則會執行,不管後續是否會使用,單例就會存在了。如果程式一定會用到這個單例,則此寫法適用。

二、懶漢式

Java:

public class LazySingleton {
    private static final String TAG = BaseSingleton.class.getSimpleName();
    private LazySingleton(){
        Log.e(TAG,"LazySingleton init");
    }
    private static LazySingleton INSTANCE;
    public static LazySingleton getInstance(){
        if(INSTANCE == null){
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }
    public void sayHello(String name){
        Log.e(TAG,"Hello "+name+",nice to meet you !");
    }
}

Kotlin:

class KLazySingleton private constructor(){
    companion object {
        //原生寫法
        val INSTANCE by lazy(LazyThreadSafetyMode.NONE) {
            KLazySingleton()
        }
        //翻譯Java
        private var INSTANCE1:KLazySingleton? = null
        fun getInstance():KLazySingleton{
            if(INSTANCE1 == null){
                INSTANCE1 = KLazySingleton()
            }
            return INSTANCE1!!
        }
    }
    fun sayHello(name:String){
        Log.e(this::class.java.simpleName,"Hell $name,nice to meet you !")
    }
}

可以看到此種寫法靜態變數開始並不賦值,在呼叫時進行空判斷並進行賦值。那麼如果我們同時多個執行緒來呼叫,是否如我們所希望只有一個例項呢?我們寫如下呼叫程式碼:

for (i in 0..20){
  Thread(
     Runnable {
        LazySingleton.getInstance().sayHello("Calm$i")                      
        Log.e(this::class.java.simpleName,Thread.currentThread().id.toString())
     }
  ).start()
}

迴圈開啟20個執行緒來呼叫單例,其輸出結果如下:

10-12 14:07:33.920 8361-8470/com.calm.singleton E/LazySingleton: LazySingleton init
10-12 14:07:33.922 8361-8470/com.calm.singleton E/LazySingleton: Hello Calm0,nice to meet you !
10-12 14:07:33.924 8361-8471/com.calm.singleton E/LazySingleton: LazySingleton init
10-12 14:07:33.925 8361-8471/com.calm.singleton E/LazySingleton: Hello Calm1,nice to meet you !
10-12 14:07:33.926 8361-8470/com.calm.singleton E/MainActivity: 4614
10-12 14:07:33.926 8361-8471/com.calm.singleton E/MainActivity: 4615

這裡我只截取了出現問題的部分,可以看到由於多執行緒呼叫而導致單例其實是被初始化了2次的,存在2個例項,而不是我們希望的單例。可見此種寫法如果要用於多執行緒,是不合適的。

三、同步鎖

Java:

public class LazySyncSingleton {
    private static final String TAG = LazySyncSingleton.class.getSimpleName();
    private LazySyncSingleton(){
        Log.e(TAG,"LazySyncSingleton init");
    }
    private static LazySyncSingleton INSTANCE;
    public static synchronized LazySyncSingleton getInstance(){
        if(INSTANCE == null){
            INSTANCE = new LazySyncSingleton();
        }
        return INSTANCE;
    }
    public void sayHello(String name){
        Log.e(TAG,"Hello "+name+",nice to meet you !");
    }
}

Kotlin:

class KLazySyncSingleton {
    companion object {
        private var INSTANCE:KLazySyncSingleton? = null
        @Synchronized
        fun getInstance():KLazySyncSingleton{
            if(INSTANCE == null){
                INSTANCE = KLazySyncSingleton()
            }
            return INSTANCE!!
        }
    }
    fun sayHello(name:String){
        Log.e(this::class.java.simpleName,"Hell $name,nice to meet you !")
    }
}

可以看到,其實此種方式與懶漢式區別只是加上了@Synchronized 標識,完美解決多執行緒而導致的非單例問題。只是在每次獲取單例的時候都加了鎖,效率上有所犧牲。於是,第四種寫法應運而生。

四、雙重校驗

Java:

public class DoubleCheckSingleton {
    private static final String TAG = DoubleCheckSingleton.class.getSimpleName();
    private DoubleCheckSingleton(){
        Log.e(TAG,"DoubleCheckSingleton init");
    }
    private static volatile DoubleCheckSingleton INSTANCE;
    public static DoubleCheckSingleton getInstance(){
        if(INSTANCE == null){
            synchronized (DoubleCheckSingleton.class){
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckSingleton();
                }
            }
        }
        return INSTANCE;
    }
    public void sayHello(String name){
        Log.e(TAG,"Hello "+name+",nice to meet you !");
    }
}

Kotlin:

class KDoubleCheckSingleton {
    companion object {
        //原生寫法
        val INSTANCE by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            KDoubleCheckSingleton()
        }
        //翻譯寫法
        @Volatile
        private var INSTANCE1:KDoubleCheckSingleton? = null
        fun getInstance():KDoubleCheckSingleton{
            if(INSTANCE1 == null){
                synchronized(DoubleCheckSingleton::class){
                    if(INSTANCE1 == null){
                        INSTANCE1 = KDoubleCheckSingleton()
                    }
                }
            }
            return INSTANCE1!!
        }
    }
    fun sayHello(name:String){
        Log.e(this::class.java.simpleName,"Hell $name,nice to meet you !")
    }
}

此種寫法較同步鎖寫法,只在第一次獲取單例的時候加鎖,之後則不需要在加鎖,提升了第一次之後獲取單例的效率。

五、內部類

Java:

public class InnerSingleton {
    private static final String TAG = InnerSingleton.class.getSimpleName();
    private InnerSingleton(){
        Log.e(TAG,"InnerSingleton init");
    }
    private static class Holer{
        private static InnerSingleton INSTANCE = new InnerSingleton();
    }
    public static InnerSingleton getInstance(){
        return Holer.INSTANCE;
    }
    public void sayHello(String name){
        Log.e(TAG,"Hello "+name+",nice to meet you !");
    }
}

Kotlin:

class KInnerSingleton {
    private object Holder{
        val INSTANCE = KInnerSingleton()
    }
    companion object {
        fun getInstance() = Holder.INSTANCE
    }
    fun sayHello(name:String){
        Log.e(this::class.java.simpleName,"Hell $name,nice to meet you !")
    }
}

驚到了沒?依靠靜態內部類保證了執行緒同步,又滿足了在getInstance()時才建立單例的懶漢式。比較推薦的一種寫法。

單例的寫法具體使用哪一種,還是要依賴於我們實際的使用場景來進行選擇,而不是一味的哪一種好。其實他們都有各自的優缺點,依場景選擇即可。