Java多線程 8種單例模式總結(jié)
不同寫法對比
餓漢式: 寫法簡單, 但是沒有l(wèi)azy loading 懶加載
懶漢式: 如果寫的不好, 會有線程安全問題. 尤其是在判斷是否為空的地方
靜態(tài)內(nèi)部類: 可用 避免了線程安全問題和資源浪費(fèi)的問題.
雙重檢查模式: ?面試使用, 可以體現(xiàn)出很多的JMM知識 . ?同時做到了線程安全和懶加載
枚舉: 最好的寫法. 生產(chǎn)中使用.
為什么枚舉是最好的單例模式寫法
《Effective Java》 這本書的作者提到, 枚舉是單例模式的最佳寫法
枚舉的單例模式寫法簡單. 只需要創(chuàng)建一個枚舉類, 一行代碼創(chuàng)建實(shí)例就行了.
線程安全有保障: 由于枚舉是特殊的類, 不需要程序員去保障線程安全, 枚舉反編譯后, 是成為了一個final修飾的類, 并且繼承了枚舉這個父類, 并且這個父類中的實(shí)例, 都是通過static定義的, 所以枚舉的本質(zhì)就是一個靜態(tài)的對象, 那么第一次使用到枚舉的時候, 才會被加載, 有懶加載的優(yōu)點(diǎn).
避免反序列化破壞單例: ?其他單例模式的寫法, 可能會被反射的寫法所破壞. 比如通過反射會把私有的構(gòu)造方法給繞過去, 或者反序列化也會反序列化出多個實(shí)例. 此時,如果使用枚舉, 就避免了安全問題,防止反序列化創(chuàng)新創(chuàng)建新的對象. ?
使用的注意事項(xiàng)
枚舉最好用
非線程同步的方法不能使用
如果程序一開始加載的資源多, 那么就應(yīng)該使用懶加載
餓漢式的寫法, ?如果對象的創(chuàng)建需要配置文件或數(shù)據(jù)庫中的數(shù)據(jù)就不適用. ?因?yàn)橐婚_始就實(shí)例好了一些對象, 但是還沒加載好數(shù)據(jù), 實(shí)際還是不可用的, 應(yīng)該用懶漢式的懶加載
懶加載雖然好, 但是靜態(tài)內(nèi)部類這種方式會引入編程的復(fù)雜性.
單例模式面試問題
餓漢式的缺點(diǎn)
類一加載的時候,就創(chuàng)建了實(shí)例, 如果不需要這個實(shí)例, 就造成了浪費(fèi),懶漢式的缺點(diǎn)?
雖然解決了前期加載資源浪費(fèi)的問題, 只在需要的時候才加載 . 但是寫法比較的復(fù)雜. 會有可能寫成了線程不安全的情況.為什么要用雙重檢查? 不用就不安全嗎
單個檢查, 線程不安全, 兩個線程可能同時進(jìn)入單個檢查的代碼中, 造成創(chuàng)建了兩個實(shí)例對象. 雙重檢查做到了懶加載和線程安全. ?如果不用雙重鎖,把synchronized加在方法上,線程是安全的 , 那么造成性能的下降,雙重檢查為什么要加volatile?
因?yàn)闃?gòu)造單例模式, 創(chuàng)建實(shí)例對象的時候, 代表了三個步驟, 分別是創(chuàng)建一個空對象, 調(diào)用構(gòu)造方法 , 最后是把實(shí)例的地址, 分配給引用. 但是這三個步驟順序是不能保證的, 一旦調(diào)用構(gòu)造方法這一步是最后一步了, 那么很有可能分配給引用的對象, 造成空指針的異常, 這樣就造成了線程安全問題,加了volatile就避免了重排序, ?并且加上了volatile, 第一個線程創(chuàng)建的單例的實(shí)例對象, 對于第二個線程也是可見的, 保證了可見性.應(yīng)該選擇哪種單例模式的寫法最好?
用枚舉的寫法最好, 因?yàn)閷懛ê唵? 線程安全, 還能防止反序列化破壞單例.?