好心封裝工具類,結(jié)果被領(lǐng)導(dǎo)怒懟:“會(huì)不會(huì)寫代碼!”









因?yàn)榻^大部分開發(fā)者平時(shí)對(duì)單例模式的認(rèn)識(shí),可能僅僅停留在“會(huì)用”的階段。為什么會(huì)有這個(gè)模式?為什么要用這個(gè)模式?在哪里用單例模式最合適?亂用了會(huì)有什么負(fù)面影響?
這些可能大多數(shù)人都一知半解。今天就讓我們大家一起來扒光單例模式的外衣,有深度的認(rèn)識(shí)一下單例模式。

通過這篇文章你能學(xué)到什么
單例模式的定義
單例模式在Android源碼中的應(yīng)用
單例模式的九種寫法以及優(yōu)劣對(duì)比
單例模式的使用場(chǎng)景
單例模式存在的缺點(diǎn)
單例模式的定義
單例模式(Singleton Pattern):確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例,這個(gè)類稱為單例類,它提供全局訪問的方法。 單例模式是一種對(duì)象創(chuàng)建型模式。
1、實(shí)例唯一性
2、自行創(chuàng)建
3、全局訪問

說了這么多了,還不知道單例模式到底啥樣呢?接下來我們一起來著手設(shè)計(jì)這個(gè)“國(guó)王”的單例類。我們先看一下單例模式的類圖:

單例模式的九種寫法
一、餓漢式(靜態(tài)常量)
/*** 餓漢式(靜態(tài)常量)*/
class King {
private static final King kingInstance = new King();
static King getInstance() {
return kingInstance;
}
private King() {
}
}
優(yōu)點(diǎn):這種寫法比較簡(jiǎn)單,就是在類裝載的時(shí)候就完成實(shí)例化。避免了線程同步問題。
缺點(diǎn):在類裝載的時(shí)候就完成實(shí)例化,沒有達(dá)到Lazy Loading的效果。如果從始至終從未使用過這個(gè)實(shí)例,則會(huì)造成內(nèi)存的浪費(fèi)。
二、餓漢式(靜態(tài)代碼塊)
/*** 餓漢式(靜態(tài)代碼塊)*/
class King {
private static King kingInstance;
static {
kingInstance = new King();
}
private King() {
}
public static King getKingInstance() {
return kingInstance;
}
}
優(yōu)點(diǎn):這種寫法比較簡(jiǎn)單,就是在類裝載的時(shí)候就完成實(shí)例化。避免了線程同步問題。
缺點(diǎn):在類裝載的時(shí)候就完成實(shí)例化,沒有達(dá)到Lazy Loading的效果。如果從始至終從未使用過這個(gè)實(shí)例,則會(huì)造成內(nèi)存的浪費(fèi)。
三、懶漢式(線程不安全)
/*** 懶漢式(線程不安全)*/
public class King {
private static King kingInstance;
private King() {
}
public static King getKingInstance() {
if (kingInstance == null) {
kingInstance = new King();
}
return kingInstance;
}
}
優(yōu)點(diǎn):懶加載,只有使用的時(shí)候才會(huì)加載。
缺點(diǎn):但是只能在單線程下使用。如果在多線程下,一個(gè)線程進(jìn)入了if (singleton == null)判斷語句塊,還未來得及往下執(zhí)行,另一個(gè)線程也通過了這個(gè)判斷語句,這時(shí)便會(huì)產(chǎn)生多個(gè)實(shí)例。所以在多線程環(huán)境下不可使用這種方式。
四、懶漢式(線程安全)
/*** 懶漢式(線程安全,同步方法)*/
public class King {
private static King kingInstance;
private King() {
}
public static synchronized King getKingInstance() {
if (kingInstance == null) {
kingInstance = new King();
}
return kingInstance;
}
}
優(yōu)點(diǎn):懶加載,只有使用的時(shí)候才會(huì)加載,獲取單例方法加了同步鎖,保障線程安全。
缺點(diǎn):效率太低了,每個(gè)線程在想獲得類的實(shí)例時(shí)候,執(zhí)行g(shù)etInstance()方法都要進(jìn)行同步。
五、懶漢式(線程安全,同步代碼塊)
/*** 懶漢式(線程安全,同步代碼塊)*/
public class King {
private static King kingInstance;
private King() {
}
public static King getKingInstance() {
if (kingInstance == null) {
synchronized (King.class) {
kingInstance = new King();
}
}
return kingInstance;
}
}
優(yōu)點(diǎn):改進(jìn)了第四種效率低的問題。
缺點(diǎn):不能完全保證單例,假如一個(gè)線程進(jìn)入了if (singleton == null)判斷語句塊,還未來得及往下執(zhí)行,另一個(gè)線程也通過了這個(gè)判斷語句,這時(shí)便會(huì)產(chǎn)生多個(gè)實(shí)例。
六、雙重檢查(DCL)
/*** 雙重檢查(DCL)*/
public class King {
private static volatile King kingInstance;
private King() {
}
public static King getKingInstance() {
if (kingInstance == null) {
synchronized (King.class) {
if (kingInstance == null){
kingInstance = new King();
}
}
}
return kingInstance;
}
}
優(yōu)點(diǎn):線程安全;延遲加載;效率較高。
缺點(diǎn):JDK < 1.5 的時(shí)候不可用
不可用原因:由于volatile關(guān)鍵字會(huì)屏蔽Java虛擬機(jī)所做的一些代碼優(yōu)化,可能會(huì)導(dǎo)致系統(tǒng)運(yùn)行效率降低,而JDK 1.5 以及之后的版本都修復(fù)了這個(gè)問題。(面試裝逼用,謹(jǐn)記!?。。?/span>

七、靜態(tài)內(nèi)部類
/*** 靜態(tài)內(nèi)部類*/
public class King {
private King() {
}
private static class KingInstance{
private static final King KINGINSTANCE = new King();
}
public static King getInstance(){
return KingInstance.KINGINSTANCE;
}
}
優(yōu)點(diǎn):避免了線程不安全,延遲加載,效率高。
缺點(diǎn):暫無,最推薦使用。
特點(diǎn):這種方式跟餓漢式方式采用的機(jī)制類似,但又有不同。
兩者都是采用了類裝載的機(jī)制來保證初始化實(shí)例時(shí)只有一個(gè)線程。不同的地方在餓漢式方式是只要Singleton類被裝載就會(huì)實(shí)例化,沒有Lazy-Loading的作用,而靜態(tài)內(nèi)部類方式在Singleton類被裝載時(shí)并不會(huì)立即實(shí)例化,而是在需要實(shí)例化時(shí),調(diào)用getInstance方法,才會(huì)裝載SingletonInstance類,從而完成Singleton的實(shí)例化。類的靜態(tài)屬性只會(huì)在第一次加載類的時(shí)候初始化,所以在這里,JVM幫助我們保證了線程的安全性,在類進(jìn)行初始化時(shí),別的線程是無法進(jìn)入的。
八、枚舉
/*** 枚舉*/
public enum King {
KINGINSTANCE;
}
優(yōu)點(diǎn):不僅能避免多線程同步問題,而且還能防止反序列化重新創(chuàng)建新的對(duì)象。
缺點(diǎn):JDK 1.5之后才能使用。
九、容器類管理
/*** 使用容器實(shí)現(xiàn)單例模式(可以用于管理單例,有興趣的可以嘗試一下)* */
class InstanceManager {
private static Map<String, Object> objectMap = new HashMap<>();
private InstanceManager(){}
public static void registerService(String key,Object instance){
if (!objectMap.containsKey(key)){
objectMap.put(key,instance);
}
}
public static Object getService(String key){
return objectMap.get(key);
}
}
/*** 使用方式* Dog類就不貼出來了
* 自己隨便寫個(gè)就行
* 可以運(yùn)行一下看看 打印的地址是否一致
*/
class Test {
public static void main(String[] args) {
InstanceManager .registerService("dog", new Dog());
Dog dog = (Dog) InstanceManager .getService("dog");
Dog dog2 = (Dog) InstanceManager .getService("dog");
Dog dog3 = (Dog) InstanceManager .getService("dog");
Dog dog4 = (Dog) InstanceManager .getService("dog");
System.out.println(dog);
System.out.println(dog2);
System.out.println(dog3);
System.out.println(dog4);
}
}
優(yōu)點(diǎn):在程序的初始,將多種單例類型注入到一個(gè)統(tǒng)一的管理類中,在使用時(shí)根據(jù)key獲取對(duì)象對(duì)應(yīng)類型的對(duì)象。這種方式使得我們可以管理多種類型的單例,并且在使用時(shí)可以通過統(tǒng)一的接口進(jìn)行獲取操作, 降低了用戶的使用成本,也對(duì)用戶隱藏了具體實(shí)現(xiàn),降低了耦合度。
缺點(diǎn):不常用,有些麻煩

九種寫法的優(yōu)劣對(duì)比
| 名稱 | 優(yōu)點(diǎn) | 缺點(diǎn) | 是否推薦 |
|---|---|---|---|
| 餓漢式(靜態(tài)常量) | 寫法簡(jiǎn)單,類裝載時(shí)完成實(shí)例化。避免了線程同步 | 急切實(shí)例化, 容易內(nèi)存泄漏 | 可用 |
| 餓漢式(靜態(tài)代碼塊) | 同上 | 同上 | 可用 |
| 懶漢式(線程不安全) | 懶加載 | 只能在單線程下使用 | 多線程不可用 |
| 懶漢式(線程安全,同步方法) | 懶加載,方法同步鎖 | 效率低 | 不推薦用 |
| 懶漢式(線程安全,同步代碼塊) | 同上,同時(shí)改變效率低問題 | 不能完全保證單例 | 不推薦用 |
| 雙重檢查(DCL)① | 線程安全;延遲加載;效率較高 | JDK < 1.5 的時(shí)候不可用 | JDK >1.5 推薦用 |
| 靜態(tài)內(nèi)部類② | 線程安全,延遲加載,效率高。 | 暫無發(fā)現(xiàn) | 墻裂推薦 |
| 枚舉 | 寫法簡(jiǎn)單,防止反序列化 | JDK 1.5之后才能使用 | JDK >1.5 推薦用 |
| 容器實(shí)現(xiàn) | 可用管理多個(gè)單例對(duì)象 | 不常用,多創(chuàng)建了一個(gè)Map | 可用 |
①:不可用原因:由于volatile關(guān)鍵字會(huì)屏蔽Java虛擬機(jī)所做的一些代碼優(yōu)化,可能會(huì)導(dǎo)致系統(tǒng)運(yùn)行效率降低,而JDK 1.5 以及之后的版本都修復(fù)了這個(gè)問題。(面試裝逼用,謹(jǐn)記?。。。?/span>
單例模式在Android源碼中的應(yīng)用
1、EventBus中獲取實(shí)例:
private static volatile EventBus defaultInstance; public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
2、InputMethodManager獲取實(shí)例
static InputMethodManager sInstance;public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
單例模式在日常開發(fā)中的應(yīng)用場(chǎng)景
1、圖片加載
2、網(wǎng)絡(luò)請(qǐng)求
3、工具類封裝
Quote from 《 Use your singletons wisely 》 Will every application use this class exactly the same way? (keyword: exactly)
Will every application ever need only one instance of this class? (keyword: ever & one)
Should the clients of this class be unaware of the application they are part of?每一個(gè)應(yīng)用(組件/模塊)是否以完全一致的方式來使用這個(gè)類?
每一個(gè)應(yīng)用(組件/模塊)是否真的只需要這個(gè)類的一個(gè)實(shí)例呢?
對(duì)于這個(gè)類的客戶端類來說,對(duì)他們自己是應(yīng)用中的一部分這件事是否應(yīng)該保持毫無察覺的狀態(tài)呢?
單例模式的優(yōu)點(diǎn)
單例模式的缺點(diǎn)
總結(jié)



碼個(gè)蛋專屬活動(dòng):打卡入口
為什么要做這個(gè)活動(dòng)?
關(guān)注「碼個(gè)蛋」,一起打卡成長(zhǎng)

