单例设计模式详解+源代码+JDK源码应用——Java设计模式系列学习笔记
文章目錄
- 一. 基本介紹
- 二. 單例模式的八種方式
- 1. 餓漢式(靜態常量)
- 2. 餓漢式(靜態代碼塊)
- 3. 懶漢式(線程不安全)
- 4. 懶漢式(線程安全,同步方法)
- 5. 懶漢式(線程安全,同步代碼塊)
- 6. 雙重檢查
- 7. 靜態內部類
- 8. 枚舉
- 三. 單例模式在JDK應用的源碼分析
- 四. 單例模式注意事項和細節說明
一. 基本介紹
采取一定的方法保證在整個的軟件系統中,對某個類智能存在一個對象實例,并且該類只提供一個取得其對象實例的方法(靜態方法)。
比如Hibernate的SessionFactor,他充當數據存儲源的代理,并負責創建Session對象。SessionFactor并不是輕量級的,一般情況下,一個項目通常只需要一個SessionFactor就夠,這時就會使用到單例模式。
二. 單例模式的八種方式
- 餓漢式(靜態常量)
- 餓漢式(靜態代碼塊)
- 懶漢式(線程不安全)
- 懶漢式(線程安全,同步方法)
- 懶漢式(線程安全,同步代碼塊)
- 雙重檢查
- 靜態內部類
- 枚舉
1. 餓漢式(靜態常量)
優點:寫法簡單,就是在類裝載的時候就完成實例化。避免了線程同步問題。
缺點:在類裝載的時候就完成實例化,沒有達到懶加載的效果。如果從始至終都沒使用過這個實例,則會造成內存的浪費。
這種基于ClassLoader機制避免了多線程的同步問題,不過,instance在類裝載時就實例化,在單例模式中大多都是調用getInstance方法,但是導致類裝載的原因有很多種,因此不能確定有其他的方式(或其它的靜態方法)導致類裝載,這時候初始化instance就沒有達到懶加載的效果
結論:這種單例模式可用,可能造成內存浪費
package com.weirdo.principle.singleton.type1;public class SingletonTest01 {public static void main(String[] args) {//測試Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance == instance2); // trueSystem.out.println("instance.hashCode=" + instance.hashCode());System.out.println("instance2.hashCode=" + instance2.hashCode());}}//餓漢式(靜態變量) class Singleton {//1.構造器私有化,外部能newprivate Singleton() {}//2.本類內部創建對象實例private final static Singleton instance = new Singleton();//3.提供一個公有的靜態方法,返回實例對象public static Singleton getInstance() {return instance;}}2. 餓漢式(靜態代碼塊)
這種方式和2.1的方式類似,只不過將類實例化的過程放在了靜態代碼塊中,也是在類裝載的時候就執行靜態代碼塊中的代碼,初始化類的實例。優缺點和2.1是一樣的。
結論:這種單例模式可用,但是可能造成內存浪費
package com.weirdo.principle.singleton.type2;public class SingletonTest02 {public static void main(String[] args) {Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance == instance2); // trueSystem.out.println("instance.hashCode=" + instance.hashCode());System.out.println("instance2.hashCode=" + instance2.hashCode());}}//餓漢式(靜態變量)class Singleton {//1.構造器私有化,外部能newprivate Singleton() {}//2.本類內部創建對象實例private static Singleton instance;static { // 在靜態代碼塊中,創建單例對象instance = new Singleton();}//3. 提供一個公有的靜態方法,返回實例對象public static Singleton getInstance() {return instance;}}3. 懶漢式(線程不安全)
起到了懶加載的效果,但是只能在單線程下使用。
如果在多線程下,一個線程進入了if(singleton==null)判斷語句塊,還未來得及往下執行,另一個線程也通過了這個判斷語句,這時便會產生多個實例。所以在多線程環境下不可使用使用這種方式。
結論:在實際開發中,不要使用這種方式
package com.weirdo.principle.singleton.type3;public class SingletonTest03 {public static void main(String[] args) {System.out.println("懶漢式1=============>線程不安全");Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance == instance2); // trueSystem.out.println("instance.hashCode=" + instance.hashCode());System.out.println("instance2.hashCode=" + instance2.hashCode());}}class Singleton {private static Singleton instance;private Singleton() {}//提供一個靜態的公有方法,當使用到該方法時,才去創建instance//即懶漢式public static Singleton getInstance() {if(instance == null) {instance = new Singleton();}return instance;} }4. 懶漢式(線程安全,同步方法)
解決了線程安全問題,但是效率太低。每個線程在想獲得類的實例的時候,執行getInstance方法都要進行同步。而其實這個方法只執行一次實例化代碼就夠了,后面的想獲得該類的實例,直接return就行了。方法進行同步效率太低。
結論:在實際開發中,不推薦使用這種方式
package com.weirdo.principle.singleton.type4;public class SingletonTest04 {public static void main(String[] args) {System.out.println("懶漢式2=============>線程安全");Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance == instance2); // trueSystem.out.println("instance.hashCode=" + instance.hashCode());System.out.println("instance2.hashCode=" + instance2.hashCode());}}// 懶漢式(線程安全,同步方法) class Singleton {private static Singleton instance;private Singleton() {}//提供一個靜態的公有方法,加入同步處理的代碼synchronized,解決線程安全問題//即懶漢式public static synchronized Singleton getInstance() {if(instance == null) {instance = new Singleton();}return instance;} }5. 懶漢式(線程安全,同步代碼塊)
跟2.4類似,不推薦使用
package com.weirdo.principle.singleton.type4;public class SingletonTest05 {public static void main(String[] args) {System.out.println("懶漢式2=============>線程安全,同步代碼塊");Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance == instance2); // trueSystem.out.println("instance.hashCode=" + instance.hashCode());System.out.println("instance2.hashCode=" + instance2.hashCode());}}// 懶漢式(線程安全,同步方法) class Singleton {private static Singleton instance;private Singleton() {}//提供一個靜態的公有方法,解決線程安全問題//即懶漢式public static synchronized Singleton getInstance() {if(instance == null) {//同步代碼塊synchronized (Singleton.class){instance = new Singleton();}}return instance;} }6. 雙重檢查
Double-Check概念是多線程開發中常使用到的,進行了兩次if(singleton==null),這樣就可以保證線程安全了。
實例化代碼只用執行一次,后面再次訪問時,判斷if(singleton==null),直接return實例化對象,也避免了反復進行方法同步。
線程安全、延遲加載、效率高。
結論:在實際開發中,推薦使用這種單例設計模式
package com.weirdo.principle.singleton.type6;public class SingletonTest06 {public static void main(String[] args) {System.out.println("雙重檢查");Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance == instance2); // trueSystem.out.println("instance.hashCode=" + instance.hashCode());System.out.println("instance2.hashCode=" + instance2.hashCode());} }// 懶漢式(線程安全,同步方法) class Singleton {private static volatile Singleton instance;private Singleton() {}//提供一個靜態的公有方法,加入雙重檢查代碼,解決線程安全問題,同事解決懶加載問題//同時保證了效率,推薦使用public static synchronized Singleton getInstance() {if(instance == null) {synchronized (Singleton.class) {if(instance == null) {instance = new Singleton();}}}return instance;} }7. 靜態內部類
這種方式采用了類裝載的機制來保證初始化實例時只有一個線程。
靜態內部類方式在Singleton類被裝載時并不會立即實例化,而是在需要實例化時,調用getInstance方法,才回裝載SingletonInstance類,從而完成Singleton的實例化。
類的靜態屬性只會在第一次加載類的時候初始化,所以在這里,JVM保證了線程的安全性,在類進行初始化時,別的線程是無法進入的。
避免了線程不安全,利用靜態內部類特點實現延遲加載,效率高。
結論:推薦使用
package com.weirdo.principle.singleton.type7;public class SingletonTest07 {public static void main(String[] args) {System.out.println("使用靜態內部類完成單例模式");Singleton instance = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance == instance2); // trueSystem.out.println("instance.hashCode=" + instance.hashCode());System.out.println("instance2.hashCode=" + instance2.hashCode());}}// 靜態內部類完成,推薦使用 class Singleton {private static volatile Singleton instance;//構造器私有化private Singleton() {}//寫一個靜態內部類,該類中有個靜態數據 Singletonprivate static class SingletonInstance {private static final Singleton INSTANCE = new Singleton(); }//提供一個靜態的公有方法,直接返回SingletonInstance.INSTANCEpublic static synchronized Singleton getInstance() {return SingletonInstance.INSTANCE;} }8. 枚舉
借助了JDK1.5中添加的枚舉來實現單例模式。不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象。
結論:推薦使用
package com.weirdo.principle.singleton.type8;public class SingletonTest08 {public static void main(String[] args) {Singleton instance = Singleton.INSTANCE;Singleton instance2 = Singleton.INSTANCE;System.out.println(instance == instance2);System.out.println(instance.hashCode());System.out.println(instance2.hashCode());instance.sayOK();} }//使用枚舉實現單例 enum Singleton {INSTANCE; //屬性public void sayOK() {System.out.println("ok~");} }三. 單例模式在JDK應用的源碼分析
java.lang.Runtime就是經典的單例模式(餓漢式)
四. 單例模式注意事項和細節說明
- 單例模式保證了系統內存中該類只存在一個對象,節省了系統資源,對于一些需要頻繁創建銷毀的對象,使用單例模式可以提高系統性能。
- 當想實例化一個單例類的時候,必須要記住使用相應的獲取對象的方法,而不是new
- 單例模式使用的場景:需要頻繁的進行創建和銷毀對象、創建對象時耗過多或耗費資源過多(即:重量級對象),但有經常用到的對象、工具類對象、頻繁訪問數據庫或文件的對象(比如數據源、session工廠等)
總結
以上是生活随笔為你收集整理的单例设计模式详解+源代码+JDK源码应用——Java设计模式系列学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是51%算力攻击?——区块链系列学习
- 下一篇: 深入浅出解释深拷贝、浅拷贝、对象拷贝、引