摘要:單例模式是建立類型中經常使用的一種設計模式。該模式下的類有且僅有一個實例。
說到單例模式,其實你們應該都不陌生,由於真的太經常使用了,應該全部開發者接觸設計模式的第一個模式。那我這裏一句話簡單說下爲什麼使用單例:若是你但願你的某個類只須要有一個實例對象,而且全局共享,那麼你就使用單例。java
單例模式是建立類型中經常使用的一種設計模式。該模式下的類有且僅有一個實例。單例模式常見的實現有懶漢式、餓漢式這兩種方式,可是在這裏,我不想討論這兩種方式,由於常見因此沒有討論和須要思考的價值。web
讓咱們來看看如下的幾種方式的一些實現機制:數據庫
上代碼:編程
開發、單例模式、線程、segmentfault
DCL雙重加鎖的方式保證每次調用getSingleton方法的時候都是同步的。其實加鎖你們都能理解,就是解決多線程同步的問題。但其實這裏有個重點,就是這行代碼:設計模式
private volatile static Singleton singleton
tomcat
爲何要用volatile去修飾呢,這邊從兩個方面去說明:安全
1.若是不用volatile修飾會怎麼樣?多線程
這看起來彷佛也是行的通的,可是瞭解過編譯器和程序指令的話就會知道那是不可靠的,具體緣由以下:框架
簡單理解,那就是如今都0202年了,一臺計算機cpu和內核都是好幾個出現的,不在是那個單核的老時代了,因此java文件編譯成字節碼指令以後,你的編碼邏輯確實是串行的,計算機也會根據範式把你編程的邏輯結果給你執行返回,可是具體到cpu去執行指令的時候,爲了體現多核的優點,會對一些指令作並行處理,以加快程序運行速度。
我想好奇的你仍是想知道,若是不加volatile的話,會在何時出現問題,那我給你說說問題出現的順序:
至於線程B返回以後會發生什麼,可想而知,沒實例化完,那麼就會致使調用部分的方法的時候,就會有空指針的異常,因此就是我上面說的,不可靠。
2.volatile做用是啥?
爲了解決這個問題,JDK1.6以後的版本提供了該關鍵字, 其實就是爲了讓其修飾的變量你可以在線程間可見,而所謂的可見,那就是你們都從主存中獲取,至於主存等概念在這裏就不展開說明了。
能夠這麼理解:在線程B讀取volatile變量後,線程A在寫這個volatile以前,全部可見的共享變量的值都將當即變得對線程B可見。
對應上面的問題解決也就是:線程A在未初始化完,singleton變量那就是null,線程B讀到的也就是null,那麼當線程B再進去想要加鎖實例化的時候,發現線程A獲取了鎖正在實例化,那就阻塞了起來,直到A實例化完釋放鎖,可是由於實例化完以後B立馬又知道該變量不爲null了因此在第二個判斷的時候,就不用進去new了,返回了。
上代碼:
靜態內部類是一個我比較喜歡的實現方式,固然很明顯代碼少,邏輯較爲簡單。這種方式主要是利用了classloader機制來保證初始化singleton的時候只有一個線程,避免了須要再去保證線程同步的問題。同時咱們把這種方式實例化有lazy loading的效果,其實主要是由於靜態內部類Holder類並不會在Singleton類被裝載的時候就被初始化了,只有當Holder類被主動使用,也就是調用了getSingleton方法以後,纔會顯示的裝載Holder類,從而實例化singleton對象。若是singleton對象是一個消耗資源佔用比較大的內存的對象的時候,若是你但願延遲加載的話,那麼這種方式是個不錯的選擇。
可是其實靜態內部類的方式實際上並無想象中的那麼完美,由於它沒法阻擋反射和反序列攻擊,你能夠利用前面兩種方式再去構造新的Singleton的實例,因此不是嚴格意義上的單例。
上代碼:
這種方式是Josh Bloch提倡的,利用枚舉的特性,讓JVM來保證線程安全和單例的問題,還能防止反序列化和反射,除了你們不怎麼經常使用外,其實這種簡單的方式是個很好的方式。
反編譯看一下,其實枚舉是在static塊中進行的對象的建立:
優勢:
1.提供了惟一實例的受控訪問。
2.由於只有一個實例,節約了系統資源,提升系統性能。
缺點:
1.單例模式沒有抽象層,擴展比較困難。
2.單例類的職責太重,違背了「單一職責原則」。
個人推薦:
咱們去使用單例基本目標就是爲了節省內存資源,並且通常的web項目都會引入Spring框架,經過Spring實現的單例和上面設計模式說的單例有所不一樣。設計模式的單例是在整個Java應用中只有一個實例,而Spring中的單例是在一個IOC容器中就只有一個單例。但對於web應用來講,web容器(Jetty或tomcat)對用戶的每一個請求都會建立一個單獨的servlet線程去處理請求,Spring框架下的接口每一個action也都是單例的,那麼其實就保證了咱們使用的是一個實例。
同時Spring也支持咱們經過註解或者xml進行lazy-init,也能夠指定scope肯定其是否爲全局單例,又或者是多個實例,對於程序來講有了更多的選擇。
固然上面提到的線程安全的問題,其實大多數狀況下Spring是沒有去保證全部bean的線程安全,因此主動權交給了開發者,咱們本身編寫程序要保證線程安全的。不過在咱們常用的數據庫dao層的那些dao 的bean對象,Spring經過ThreadLocal對象,區別與咱們經常使用的加鎖的方式而是用空間換時間,給每一個線程分配了獨自的變量副本,從而隔離了多線程訪問對數據訪問的衝突,保證了線程安全性。至於這個類和這個機制,這裏就不展開談了,談多了這篇文章就裝不下了。