Android中的單例模式(包含Java、Kotlin)
在Android開發工程中,單例模式可以說是我們使用得非常頻繁的設計模式了。常見的寫法有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()時才建立單例的懶漢式。比較推薦的一種寫法。
單例的寫法具體使用哪一種,還是要依賴於我們實際的使用場景來進行選擇,而不是一味的哪一種好。其實他們都有各自的優缺點,依場景選擇即可。