作者: 點先生, 時間: 2018.7.25
題外話
上一次被人說文章名字取得不霸氣,於是這一次我採用了這麼霸氣的名字,但實際上我是一個很低調的人。設計模式剛入門的小夥伴可以先看看這篇《設計模式入門》,在文章末尾也將列出“設計模式系列”文章。歡迎大家關注留言投幣丟香蕉。
什麼是單例模式
- 單例模式是設計模式中最簡單的形式之一。
- 一個類有且僅有一個物件例項,並自行例項化向整個系統提供。
為何要學習單例模式
- 有些物件我們只需要一個。例如:執行緒池,快取,對話方塊等等,建立太多此類例項可能會導致程式行為異常、資源使用過量等問題。
- 防止造成資源浪費。靜態變數也可以給整個系統提供一個例項,但此物件在程式一開始就被建立好了,萬一在某次執行中沒有使用到此物件,資源就被浪費了。
- 沒有富蘿莉包養我。
走進單例模式
單例模式有三個要點,分別是:
- 僅有一個例項。
- 自動例項化。
- 向整個系統提供。
這也就是我們在寫單例模式時候必須要遵守的幾點。根據上面的條件,我們能夠得出:
- 構造方法必須私有化,禁止外部隨意new Singleton();
- 類內部例項化一個例項物件。
- 對外提供一個可以獲取內部唯一例項物件的方法。
懶漢式
懶漢式這名字的精髓在於懶,在程式碼中,這種懶代表著需要的時候才建立例項,不需要就不建立了,這樣保證了資源的不浪費。
Java懶漢式
public class JavaSingleton {
private JavaSingleton(){}//私有構造方法
private static JavaSingleton instance;//提供一個例項物件
public static JavaSingleton getInstance(){//對外提供可獲取唯一例項化物件的方法
if(instance == null)
instance = new JavaSingleton(); //延遲例項化
return instance;
}
}
複製程式碼
Kotlin懶漢式1
class KotlinSingleton private constructor() {
private var instance : KotlinSingleton? = null
fun getInstance(): KotlinSingleton? {
if(instance == null)
instance = KotlinSingleton()
return instance
}
}
複製程式碼
Kotlin懶漢式2
class KotlinSingleton private constructor() {
companion object {
val instance by lazy(LazyThreadSafetyMode.NONE) {
KotlinSingleton()
}
}
}
// 在Kotlin 中呼叫
KotlinSingleton.instance.xx()
// 在Java 中呼叫
KotlinSingleton.Companion.getInstance().xx()
複製程式碼
kotlin方式1就是直譯了java方式,沒啥說的。kotlin方式2寫著很簡潔,也很明瞭。要提一點的就是companion object,他類似public static,這一點從下面呼叫方式也看得出來。lazy屬性表明是懶載入方式,LazyThreadSafetyMode.NONE表明了這種方式,執行緒不安全。
為啥不安全??!!
我們已經按照單例模式三個要點寫出了單例模式,其實都已經講完了。但為何還有其他一些寫法呢? 因為我們是程式設計師啊!優化!優化!優化! 單執行緒程式下,懶漢式完全夠用。但實際開發哪還有什麼單執行緒程式。既然存在多執行緒,就存在併發性,如果兩個執行緒同時進入非空判斷,問題就出現了。執行緒一可能建立了一個instance,執行緒二因為已經非空判斷為空了,所以也建立了個instance。這樣程式肯定不會朝著你預想的方向去。所以我們可以給非空判斷加一把鎖,防止多執行緒同時進入非空判斷。
同步鎖
Java方式
public class JavaSingleton {
private JavaSingleton(){}//私有構造方法
private static JavaSingleton instance;//提供一個例項物件
public static synchronized JavaSingleton getInstance(){//對外提供可獲取唯一例項化物件的方法
if(instance == null)
instance = new JavaSingleton(); //延遲例項化
return instance;
}
}
複製程式碼
Kotlin方式
class KotlinSingleton private constructor() {
private var instance : KotlinSingleton? = null
@Synchronized //用註解就okk了。
fun getInstance(): KotlinSingleton? {
if(instance == null)
instance = KotlinSingleton()
return instance
}
}
複製程式碼
簡單!粗暴!太粗暴了!Synchronized 是一把重型鎖,對效能影響相當的大,像單例這樣在軟體中可能多次獲取,那效能將會大大地降低!所以,這種方式不建議使用。
餓漢式
為了解決多執行緒的問題,又不想降低效能,“餓漢式”就來了。餓漢式名字精髓在於“餓”,迫切的想吃東西,在程式碼中表示,迫切的想有一個單例物件!即在初始化的時候,就把單例建立好,之後要用就直接return,但是不用就浪費了。這也是餓漢式的缺點。
Java方式
public class JavaSingleton {
private JavaSingleton(){}//私有構造方法
private static JavaSingleton instance = new JavaSingleton();//提供一個例項物件
public static JavaSingleton getInstance(){//對外提供可獲取唯一例項化物件的方法
return instance;
}
}
複製程式碼
Kotlin方式
object KotlinSingleton {
//null
}
// 在Kotlin 中呼叫
KotlinSingleton.xx()
// 在Java 中呼叫
KotlinSingleton.INSTANCE.xx()
複製程式碼
酥糊!Kotlin!有牌面!
雙重檢查鎖
前面講的三種方式都有不同程度的缺點。而雙重檢查鎖,既保證了延遲載入不浪費資源,又保證了較好的效能。但雙重檢查鎖不適用於1.4以及更早版本的java。
Java方式
public class JavaSingleton {
private JavaSingleton(){}
//volatile確保了當instance初始化為JavaSingleton例項時,多個執行緒正確的處理instance變數。
private volatile static JavaSingleton instance;
public static JavaSingleton getInstance(){
if(instance == null){
synchronized (JavaSingleton.class){//只有第一次才徹底執行鎖塊程式碼
if(instance == null){
instance = new JavaSingleton();
}
}
}
return instance;
}
}
複製程式碼
Kotlin方式1
class KotlinSingleton private constructor() {
@Volatile //用註解就okk了
private var instance : KotlinSingleton? = null
fun getInstance(): KotlinSingleton? {
if(instance == null){
synchronized(KotlinSingleton::class){
if(instance == null){
instance = KotlinSingleton()
}
}
}
return instance
}
}
複製程式碼
Kotlin方式2
class KotlinSingleton private constructor() {
companion object {
val instance by lazy (mode = LazyThreadSafetyMode.SYNCHRONIZED){
KotlinSingleton()
}
}
}
// 在Kotlin 中呼叫
KotlinSingleton.instance.xx()
// 在Java 中呼叫
KotlinSingleton.Companion.getInstance().xx()
複製程式碼
“雙重檢查鎖”的沒有明顯的缺點,如果非要說一個,可能就是太複雜了。kotlin還好,java裡面寫著相當的複雜。如果程式對效能沒有考慮的話,這樣寫顯然就太麻煩了。
關於LazyThreadSafetyMode
延遲屬性 Lazy 可選LazyThreadSafetyMode三種模式:
- SYNCHRONIZED —— 雙重檢查鎖式,預設使用。
- PUBLICATION —— 允許多個執行緒同時初始化例項,但只採用最先返回的例項。
- NONE —— 沒有任何的執行緒安全的保證和開銷。
內部類式(店長推薦!!)
不要私信問我店長是誰! 總之這種方式,解決了延遲載入,執行緒安全的問題,還程式碼量少!簡直美滋滋!但跟之前不同的是,沒有宣告例項物件。
Java方式
public class JavaSingleton {
private JavaSingleton(){}
private static class Holder{
private static JavaSingleton instance = new JavaSingleton();
}
public static JavaSingleton getInstance(){
return Holder.instance;
}
}
複製程式碼
Kotlin方式
class KotlinSingleton private constructor() {
companion object {
fun getInstance() = Holder.instance
}
private object Holder{
val instance = KotlinSingleton()
}
}
複製程式碼
在類載入時,因為沒有呼叫getInstance()所以Holder也不會載入,這樣就實現了懶載入。呼叫getInstance()時,JVM會主動保證類載入的安全性,所以執行緒也是安全的。kotlin的寫法就是java的翻譯版本。
民間大神版本
上面幾種方式都是比較官方的版本,下面介紹幾個民間版本,是真大神還是抖機靈,自行判斷。我也去研究研究!
來自知乎@丌冰: “我覺得這樣就好了,什麼懶載入,雙重什麼什麼,什麼什麼的”
fun main(args: Array<String>) {
Instance.INSTANCE.fun1()
print(Instance.INSTANCE.fun2())
}
enum class Instance {
INSTANCE;
fun fun1(){
}
fun fun2():Any?{
return null
}
}
作者:丌冰
連結:https://www.zhihu.com/question/52377186/answer/303561470
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
複製程式碼
可見Android官方並不推薦使用列舉,佔用記憶體較多。
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private Singleton() {
}
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;
}
}
複製程式碼
這也是比較少見的單例寫法,將多個單例放在進SingletonManager 的靜態map中統一管理。我是覺得有點太過於複雜,容易出錯。
以上版本僅供參考,要是用了對程式造成了什麼不好的影響,別找我。
關於單例的其他問題
關於繼承: 不能繼承!別想了。
關於單例: 儘量少使用單例。
關於我還沒想到的問題: 歡迎加群討論557247785。
總結
單例方式 | 優點 | 缺點 |
---|---|---|
懶漢式 | 懶載入 | 執行緒不安全 |
同步鎖 | 執行緒安全 | 多次獲取的效能很低 |
餓漢式 | 簡單、易寫 | 可能引起資源浪費 |
雙重檢查鎖 | 第一次獲取才加鎖 | 寫法複雜 |
內部類式 | 簡單、易寫 | 暫無 |
民間大神式 | 歡迎留言跟我討論 | 或者加qq群:557247785 |
以下是我“設計模式系列”文章,歡迎大家關注留言投幣丟香蕉。