只有光頭才能變強html
回顧前面:java
原本打算沒那麼快更新的,這陣子在刷Spring的書籍。在看Spring的時候又常常會看到「單例」,「工廠」這些字樣。程序員
因此,就先來講說單例和工廠設計模式啦,這兩種模式也是很常見的,我看不少面經都會遇到這兩種模式~設計模式
本文主要講解單例設計模式,若是有錯的地方但願能多多包涵,並不吝在評論區指正!安全
單例模式定義很簡單:一個類中能建立一個實例,因此稱之爲單例!微信
那咱們何時會用到單例模式呢??多線程
學過Java Web的同窗可能就知道:函數
那既然多例是頻繁建立對象、須要管理對象的,那Struts2爲何要多例呢??post
能使用一個對象來作就不用實例化多個對象!這就能減小咱們空間和內存的開銷~性能
那有可能有的人又會想了:咱們使用靜態類.doSomething()
和使用單例對象調用方法的效果是同樣的啊。
靜態類.doSomething()
體現的是基於對象,而使用單例設計模式體現的是面向對象。編寫單例模式的代碼其實很簡單,就分了三步:
根據上面的步驟,咱們就能夠輕鬆完成建立單例對象了。
public class Java3y { // 1.將構造函數私有化,不能夠經過new的方式來建立對象 private Java3y(){} // 2.在類的內部建立自行實例 private static Java3y java3y = new Java3y(); // 3.提供獲取惟一實例的方法 public static Student getJava3y() { return java3y; } }
這種代碼咱們稱之爲:「餓漢式」:
既然說一上來就建立對象,若是沒有用過會形成內存浪費:
public class Java3y { // 1.將構造函數私有化,不能夠經過new的方式來建立對象 private Java3y(){} // 2.1先不建立對象,等用到的時候再建立 private static Java3y java3y = null; // 2.1調用到這個方法了,證實是要被用到的了 public static Java3y getJava3y() { // 3. 若是這個對象引用爲null,咱們就建立並返回出去 if (java3y == null) { java3y = new Java3y(); } return java3y; } }
上面的代碼行不行??在單線程環境下是行的,在多線程環境下就不行了!
要解決也很簡單,咱們只要加鎖就行了:
上面那種直接在方法上加鎖的方式其實不夠好,由於在方法上加了內置鎖在多線程環境下性能會比較低下,因此咱們能夠將鎖的範圍縮小。
public class Java3y { private Java3y() { } private static Java3y java3y = null; public static Java3y getJava3y() { if (java3y == null) { // 將鎖的範圍縮小,提升性能 synchronized (Java3y.class) { java3y = new Java3y(); } } return java3y; } }
那上面的代碼可行嗎??不行,由於雖然加了鎖,但仍是有可能建立出兩個對象出來的:
getJava3y()
方法,他們同時判斷java==null
,得出的結果都是爲null,因此進入了if代碼塊了有的同窗可能以爲我瞎吹比,明明加鎖了還不行?咱們來測試一下:
public class TestDemo { public static void main(String[] args) { // 線程A new Thread(() -> { // 建立單例對象 Java3y java3y = Java3y.getJava3y(); System.out.println(java3y); }).start(); // 線程B new Thread(() -> { // 建立單例對象 Java3y java3y = Java3y.getJava3y(); System.out.println(java3y); }).start(); // 線程C new Thread(() -> { // 建立單例對象 Java3y java3y = Java3y.getJava3y(); System.out.println(java3y); }).start(); } }
能夠看到,打印出的對象不僅僅只有一個的!
厲害的程序員又想到了:進入同步代碼塊時再判斷一下對象是否存在就穩了吧!
public class Java3y { private Java3y() { } private static Java3y java3y = null; public static Java3y getJava3y() { if (java3y == null) { // 將鎖的範圍縮小,提升性能 synchronized (Java3y.class) { // 再判斷一次是否爲null if (java3y == null) { java3y = new Java3y(); } } } return java3y; } }
其實還不穩!這裏會有重排序的問題:
原本想測試重排序問題的效果的,一直沒測試出來~~~有相關測試代碼的但願能夠告訴我怎麼能測出來....
要解決也十分簡單,加上咱們的volatile關鍵字就能夠了,volatile有內存屏障的功能!
具體可參考資料:
因此說,完整的DCL代碼是這樣子的:
public class Java3y { private Java3y() { } private static volatile Java3y java3y = null; public static Java3y getJava3y() { if (java3y == null) { // 將鎖的範圍縮小,提升性能 synchronized (Java3y.class) { // 再判斷一次是否爲null if (java3y == null) { java3y = new Java3y(); } } } return java3y; } }
再說明:
還可使用靜態內部類這種巧妙的方式來實現單例模式!它的原理是這樣的:
getInstance()
時,都會使SingletonHolder被加載和被初始化,此時靜態初始化器將執行Singleton的初始化操做。(被調用時才進行初始化!)public class Java3y { private Java3y() { } // 使用內部類的方式來實現懶加載 private static class LazyHolder { // 建立單例對象 private static final Java3y INSTANCE = new Java3y(); } // 獲取對象 public static final Java3y getInstance() { return LazyHolder.INSTANCE; } }
靜態內部類這種方式是很是推薦使用的!不少人沒接觸過單例模式的都不知道有這種寫法,這種寫法很優化也高效!
參考資料:
使用枚舉就很是簡單了:
public enum Java3y3y { JAVA_3_Y_3_Y, }
那這種有啥好處??枚舉的方式實現:
這種也較爲推薦使用!
總的來講單例模式寫法有5種:
明天估計寫的是工廠模式了,敬請期待哦~~~
參考資料:
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:Java3y。爲了你們方便,剛新建了一下qq羣:742919422,你們也能夠去交流交流。謝謝支持了!但願能多介紹給其餘有須要的朋友
文章的目錄導航: