小司機帶你學習單例模式的六種姿式!

單例模式是建立型模式的一種,下面總結一下在 Java 中實現單例模式的幾種方法,並在多線程環境中進行了測試。

1、單例模式概念

單例模式確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例,這個類稱爲單例類,它提供了全局訪問的方法。git

單例模式的三個特色:github

  • 這個類只能有一個實例。
  • 這個類自行建立這個實例。
  • 這個類自行向整個系統提供這個實例。

單例模式的應用有,Windows 裏的任務管理器(一個系統中只有一個)、網站計數器(實現多個頁面的計數同步)、數據庫鏈接池(減小資源損耗)等。數據庫

2、單例模式實現

根據單例模式的特色,咱們來實現單例模式,在類中提供一個靜態方法來獲取這個惟一的實例對象,給其餘類提供實例,而且這個實例對象不能直接使用 new 建立,因此構造方法要聲明成私有,這即是最簡單的單例模式實現。這樣的實如今單線程環境中,固然沒問題,可是咱們還要考慮多線程環境下的安全實現。編程

下面是對單例模式的各類實現,而且對每種實現方法都在多線程環境中作了測試,全部代碼都在個人 GitHub 倉庫中中,傳送門。該倉庫還在完成中,用 Java 實現 23 種設計模式,並對設計原則和每種設計模式作出詳細的分析,感興趣的能夠 fork 或者 star 哦,也歡迎小夥伴參與該倉庫的完成。設計模式

2.1 懶漢式單例類(線程不安全)

經過 getInstance() 方法獲得單例對象,單例對象在須要的時候才被延遲建立,因此稱之爲懶漢式。可是在多線程環境中,因爲這個 getInstance() 方法可能被多個線程同時調用,這極可能會建立多個實例,因此這種實如今多線程環境下是不安全的。安全

懶漢式-線程不安全.jpg

2.2 懶漢式單例類(線程安全)

給 getInstance() 加上 synchronized 關鍵字後,能夠保證這個方法在同一時間只能被一個線程調用,多個線程調用這個方法要排隊依次調用,這就保證了只會建立一個單例對象,在多線程環境下是安全的。微信

懶漢式-線程安全.jpg

2.3 餓漢式單例類

相比於上面的懶漢式,餓漢式在類加載的時候就會建立實例對象,在 getInstance() 方法直接返回建立好的對象,簡單直接,在多線程環境下也是安全的。 多線程

餓漢式.jpg

2.4 雙重校驗鎖單例類

針對於上面的線程安全的懶漢式加載,這種實現方式不是直接給方法加上 synchronized 關鍵字,而是在 getInstance() 方法作雙重檢查來解決線程不安全的問題。這種方式容許多個線程同時調用該方法,可是在方法中會進行兩次檢查,第一次檢查實例是否已經存在,若是不存在才進入下面的同步代碼塊,線程安全的建立實例,若是實例真的不存在(避免這是有其餘線程建立好了,再次建立新的實例)纔會建立實例。這種方式理論上要比直接使用 synchronized 關鍵字性能要高,可是對於不一樣虛擬機對 volatile 關鍵字的優化,優點並不明顯。性能

雙重校驗式.jpg

2.5 靜態內部類單例類

建立一個靜態內部類,來建立實例,和上面餓漢式相比,雖然都是直接 new 實例,可是這種方式在外部類加載時,靜態內部類並不會被加載。只有在第一次調用 getInstance() 方法時,纔會顯式的加載靜態內部類,建立實例,也是一種延遲(懶)建立方式。測試

靜態內部類.jpg

2.6 枚舉單例類

枚舉實現單例模式是 Java 大牛們比較推薦的,由於這種方式實現很是簡單,而且這種方式可是大多數單例模式的實現並非這種方式。這種方式須要開發者對枚舉有清晰的認識,這裏也簡單的回顧一下枚舉的基本知識。

枚舉是在 Java1.5 以後出現的,能夠更加簡單的定義常量,經過反編譯,咱們能夠發現枚舉其實也是一個 Java 類,這個類繼承自 Enum 接口,定義的枚舉對象會被加上 static final 關鍵字,這就是咱們不用枚舉時聲明常量的方式,另外在 static 靜態代碼塊中初始化枚舉對象,枚舉的構造方法被加上了 private 關鍵字,防止其餘類建立新的枚舉對象實例。雖然前面幾種方式沒法直接使用 new 建立新的實例,可是能夠用反射來繞過 private 限制,而枚舉卻有自帶的序列化機制、防止反射攻擊形成屢次實例化、線程安全的優勢,從這些地方咱們均可以看出使用枚舉是實現單例模式的絕佳方式。

枚舉式.jpg

3、單例模式總結

根據對資源加載時機的須要,來選擇合適的單例模式實現方式,若是是懶加載方式,能夠選擇懶漢方式和雙重校驗鎖方式;若是將資源加載的時間提早來達到使用時的快速體驗,能夠選擇餓漢方式;若是涉及到序列化建立單例對象,能夠選擇枚舉方式。

單例模式的優勢是提供了對惟一實例的訪問控制,能夠節約系統資源,但缺點是單例類的職責太重,而且缺乏抽象層難以擴展,不太符合單一職責原則。

想看更多編程文章,歡迎關注下方的微信公衆號哦。

編程心路
相關文章
相關標籤/搜索