極客思考設計模式:你確定你真的理解了單例模式嗎?

      網友投稿 637 2022-05-30

      什么是單例模式?

      說到單例模式,其實大家應該都不陌生,因為真的太常用了,應該所有開發者接觸設計模式的第一個模式。那我這里一句話簡單說下為何使用單例:如果你希望你的某個類只需要有一個實例對象,并且全局共享,那么你就使用單例。

      我喜歡的單例模式實現

      單例模式常見的實現有懶漢式、餓漢式這兩種方式,但是在這里,我不想討論這兩種方式,因為常見所以沒有討論和需要思考的價值。

      讓我們來看看以下的幾種方式的一些實現機制:

      一、雙重校驗鎖(DCL)

      上代碼:

      DCL雙重加鎖的方式保證每次調用getSingleton方法的時候都是同步的。其實加鎖大家都能理解,就是解決多線程同步的問題。但其實這里有個重點,就是這行代碼:

      private?volatile?static?Singleton?singleton

      為什么要用volatile去修飾呢,這邊從兩個方面去說明:

      1.如果不用volatile修飾會怎么樣?

      這看起來似乎也是行的通的,但是了解過編譯器和程序指令的話就會知道那是不可靠的,具體原因如下:

      1)編譯器優化了程序指令,以加快cpu處理速度。

      2)多核cpu會動態調整表指令順序,以加快并行運算能力。

      簡單理解,那就是現在都0202年了,一臺計算機cpu和內核都是好幾個出現的,不在是那個單核的老時代了,所以java文件編譯成字節碼指令之后,你的編碼邏輯確實是串行的,計算機也會根據范式把你編程的邏輯結果給你執行返回,但是具體到cpu去執行指令的時候,為了體現多核的優勢,會對一些指令做并行處理,以加快程序運行速度。

      我想好奇的你,還是想知道,如果不加volatile的話,會在什么時候出現問題,那我給你說說問題出現的順序:

      1)線程A,調用方法獲取實例,發現對象未實例化,準備開始實例化。

      2)由于編譯器優化了程序的指令,允許對象在構造函數未調用完成前,將共享變量的引用指向部分構造的對象,雖然對象未完全實例化,但是已經不為null了。

      3)線程B進入也要調用方法獲取實例,發現部分構造的對象已經不為null,則直接返回了該對象。

      至于線程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對象,區別與我們常用的加鎖的方式而是用空間換時間,給每個線程分配了獨自的變量副本,從而隔離了多線程訪問對數據訪問的沖突,保證了線程安全性。至于這個類和這個機制,這里就不展開談了,談多了這篇文章就裝不下了。

      Java 任務調度 容器

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:clickhouse服務外網無法訪問
      下一篇:一個來自“天府之國”的邀約
      相關文章
      亚洲男同gay片| 亚洲欧洲日韩在线电影| 亚洲人成7777| 亚洲伊人久久大香线焦| 亚洲精品福利网站| 亚洲毛片基地日韩毛片基地| 久久精品国产亚洲AV麻豆网站 | 亚洲精品国产suv一区88| 久久久久久亚洲精品影院| 激情综合亚洲色婷婷五月| 亚洲国产精品不卡在线电影| 久久亚洲私人国产精品vA| 亚洲∧v久久久无码精品| 亚洲狠狠综合久久| 日韩精品一区二区亚洲AV观看| 久久久久亚洲av无码专区喷水 | 亚洲精品午夜国产va久久| 亚洲第一区二区快射影院| 亚洲日本va一区二区三区| 亚洲国产精品成人综合色在线| 亚洲国产精品无码第一区二区三区| 亚洲色偷偷偷综合网| 亚洲av无码专区首页| 亚洲va中文字幕无码| 亚洲日韩欧洲乱码AV夜夜摸| 亚洲真人无码永久在线| 亚洲人成无码www久久久| 亚洲狠狠爱综合影院婷婷| 国产亚洲大尺度无码无码专线| 亚洲精品无码久久一线| 亚洲?V乱码久久精品蜜桃| 亚洲日韩中文字幕一区| 国产偷v国产偷v亚洲高清| 色偷偷亚洲第一综合网| 在线观看亚洲av每日更新| 国产亚洲精品2021自在线| 亚洲中文字幕日本无线码| 亚洲四虎永久在线播放| 国产亚洲福利精品一区| 亚洲女人被黑人巨大进入| jizzjizz亚洲日本少妇|