摘要:使用靜態類體現的是基于對象,而使用單例設計模式體現的是面向對象。二編寫單例模式的代碼編寫單例模式的代碼其實很簡單,就分了三步將構造函數私有化在類的內部創建實例提供獲取唯一實例的方法餓漢式根據上面的步驟,我們就可以輕松完成創建單例對象了。
前言
只有光頭才能變強
回顧前面:
給女朋友講解什么是代理模式
包裝模式就是這么簡單啦
本來打算沒那么快更新的,這陣子在刷Spring的書籍。在看Spring的時候又經常會看到“單例”,“工廠”這些字樣。
所以,就先來說說單例和工廠設計模式啦,這兩種模式也是很常見的,我看很多面經都會遇到這兩種模式~
本文主要講解單例設計模式,如果有錯的地方希望能多多包涵,并不吝在評論區指正!
一、單例模式概述單例模式定義很簡單:一個類中能創建一個實例,所以稱之為單例!
那我們什么時候會用到單例模式呢??
那我們想想既然一個類中只能創建一個實例了,那么可以說這是跟類的狀態與對象無關的了。
頻繁創建對象、管理對象是一件耗費資源的事,我們只需要創建一個對象來用就足夠了!
學過Java Web的同學可能就知道:
Servlet是單例的
Struts2是多例的
SpringMVC是單例的
那既然多例是頻繁創建對象、需要管理對象的,那Struts2為什么要多例呢??
主要由于設計層面上的問題,Struts2是基于Filter攔截類的,ognl引擎對變量是注入的。所以它要設計成多例的~
能使用一個對象來做就不用實例化多個對象!這就能減少我們空間和內存的開銷~
那有可能有的人又會想了:我們使用靜態類.doSomething()和使用單例對象調用方法的效果是一樣的啊。
沒錯,效果就是一樣的。使用靜態類.doSomething()體現的是基于對象,而使用單例設計模式體現的是面向對象。
二、編寫單例模式的代碼編寫單例模式的代碼其實很簡單,就分了三步:
將構造函數私有化
在類的內部創建實例
提供獲取唯一實例的方法
2.1餓漢式根據上面的步驟,我們就可以輕松完成創建單例對象了。
public class Java3y { // 1.將構造函數私有化,不可以通過new的方式來創建對象 private Java3y(){} // 2.在類的內部創建自行實例 private static Java3y java3y = new Java3y(); // 3.提供獲取唯一實例的方法 public static Student getJava3y() { return java3y; } }
這種代碼我們稱之為:“餓漢式”:
一上來就創建對象了,如果該實例從始至終都沒被使用過,則會造成內存浪費。
2.2簡單懶漢式既然說一上來就創建對象,如果沒有用過會造成內存浪費:
那么我們就設計用到的時候再創建對象!
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; } }
上面的代碼行不行??在單線程環境下是行的,在多線程環境下就不行了!
如果不知道為啥在多線程環境下不行的同學可參考我之前的博文:多線程基礎必要知識點!看了學習多線程事半功倍
要解決也很簡單,我們只要加鎖就行了:
2.3雙重檢測機制(DCL)懶漢式上面那種直接在方法上加鎖的方式其實不夠好,因為在方法上加了內置鎖在多線程環境下性能會比較低下,所以我們可以將鎖的范圍縮小。
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; } }
那上面的代碼可行嗎??不行,因為雖然加了鎖,但還是有可能創建出兩個對象出來的:
線程A和線程B同時調用getJava3y()方法,他們同時判斷java==null,得出的結果都是為null,所以進入了if代碼塊了
此時線程A得到CPU的控制權-->進入同步代碼塊-->創建對象-->返回對象
線程A完成了以后,此時線程B得到了CPU的控制權。同樣是-->進入同步代碼塊-->創建對象-->返回對象
很明顯的是:Java3y類返回了不止一個實例!所以上面的代碼是不行的!
有的同學可能覺得我瞎吹比,明明加鎖了還不行?我們來測試一下:
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有內存屏障的功能!
具體可參考資料:
https://www.zhihu.com/question/35268028----雙重檢查鎖失效是因為對象的初始化并非原子操作?
http://ifeve.com/doublecheckedlocking/---有關“雙重檢查鎖定失效”的說明
https://my.oschina.net/u/866190/blog/205454----正確使用雙重檢查鎖(DCL)
所以說,完整的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; } }
再說明:
2.4靜態內部類懶漢式還可以使用靜態內部類這種巧妙的方式來實現單例模式!它的原理是這樣的:
當任何一個線程第一次調用getInstance()時,都會使SingletonHolder被加載和被初始化,此時靜態初始化器將執行Singleton的初始化操作。(被調用時才進行初始化!)
初始化靜態數據時,Java提供了的線程安全性保證。(所以不需要任何的同步)
public class Java3y { private Java3y() { } // 使用內部類的方式來實現懶加載 private static class LazyHolder { // 創建單例對象 private static final Java3y INSTANCE = new Java3y(); } // 獲取對象 public static final Java3y getInstance() { return LazyHolder.INSTANCE; } }
靜態內部類這種方式是非常推薦使用的!很多人沒接觸過單例模式的都不知道有這種寫法,這種寫法很優化也高效!
參考資料:
https://www.zhihu.com/question/35454510/answer/62829602----java 單例模式通過內部靜態類的方式?
2.5枚舉方式實現使用枚舉就非常簡單了:
public enum Java3y3y { JAVA_3_Y_3_Y, }
那這種有啥好處??枚舉的方式實現:
簡單,直接寫就行了
防止多次實例化,即使是在面對復雜的序列化或者反射攻擊的時候(安全)!
這種也較為推薦使用!
三、總結總的來說單例模式寫法有5種:
餓漢式
簡單懶漢式(在方法加鎖)
DCL雙重檢測加鎖(進階懶漢式)
靜態內部類實現懶漢式(最推薦寫法)
枚舉方式(最安全、簡潔寫法)
明天估計寫的是工廠模式了,敬請期待哦~~~
參考資料:
《設計模式之禪》
http://www.cnblogs.com/seesea125/archive/2012/04/05/2433463.html---為什么要用單例模式?
https://zhuanlan.zhihu.com/p/32310340---圣誕節,讓我們聊聊單例模式
https://zhuanlan.zhihu.com/p/34406410---單例模式詳解
http://www.nowamagic.net/librarys/veda/detail/1776---使用單例模式需要注意的幾個問題
https://zhuanlan.zhihu.com/p/23713957---Java設計模式(一)-單例模式
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y。為了大家方便,剛新建了一下qq群:742919422,大家也可以去交流交流。謝謝支持了!希望能多介紹給其他有需要的朋友
文章的目錄導航:
https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/69406.html
摘要:,各位同學好我是吳明課堂的答疑老師之一陳婉,我又來了這次我為大家帶來了一個多列轉一列的表格案例,效果演示圖如下多列轉單列表格的三種辦法,你會幾種在 Hi,各位同學好!我是吳明課堂的答疑老師之一陳婉,我又來了!這次我為大家帶來了一個多列轉一列的表格案例,效果演示圖如下:在這個案例中,需要把數據源中的電話、電視機、手表、智能手...
摘要:有數百個免費的庫出來,為應用程序選擇正確的框架變得非常困難。是流行的驅動技術之一,由于年創建。在這三個塊中,有幾個暴露低層接口的綁定。反應由,和許多開發人員和個人的社區維護。誕生于年,是一個輕量級的框架。 有數百個免費的JS庫出來,為應用程序選擇正確的JavaScript框架變得非常困難。一些開發商最終會拋棄,而其他開發者則迅速發展,并得到廣泛采用。許多開發人員只知道像jQuery和R...
摘要:使用的好處知乎的回答不用自己組裝,拿來就用。統一配置,便于修改。 前言 只有光頭才能變強 回顧前面: 給女朋友講解什么是代理模式 包裝模式就是這么簡單啦 單例模式你會幾種寫法? 工廠模式理解了沒有? 在刷Spring書籍的時候花了點時間去學習了單例模式和工廠模式,總的來說還是非常值得的! 本來想的是刷完《Spring 實戰 (第4版)》和《精通Spring4.x 企業應用開發實戰》...
摘要:餓漢模式線程安全,調用效率高,但是不能延時加載這樣做的好處是編寫簡單,但是無法做到延遲創建對象。考慮線程安全的寫法這種寫法考慮了線程安全,將對的判斷以及的部分使用進行加鎖。如此即可從語義上保證這種單例模式寫法是線程安全的。 餓漢模式 線程安全,調用效率高,但是不能延時加載 public class ImageLoader{ private static ImageLoade...
閱讀 2485·2021-11-24 09:39
閱讀 3527·2019-08-30 15:53
閱讀 601·2019-08-29 15:15
閱讀 2909·2019-08-26 13:23
閱讀 3223·2019-08-26 10:48
閱讀 650·2019-08-26 10:31
閱讀 776·2019-08-26 10:30
閱讀 2369·2019-08-23 18:32