Java 設計模式-單例模式 理論代碼相結合

這是我參與 8 月更文挑戰的第 6 天,活動詳情查看: 8月更文挑戰java

今天就讓咱們拿Java的單例模式開篇吧,持續更新中。web

讓咱們一塊兒學習設計模式吧,說它是基礎也是基礎,說它不是,又確實不是。它穿插在各處。學好它也是爲了能讓本身更進一步吧。 很喜歡一句話:「八小時謀生活,八小時外謀發展」。數據庫

共勉設計模式

封面地點:😂我也不知道api

做者:L安全

設計模式系列markdown

1、前言

概念:

單例模式,屬於建立類型的一種經常使用的軟件設計模式。經過單例模式的方法建立的類在當前進程中只有一個實例(根據須要,也有可能一個線程中屬於單例,如:僅線程上下文內使用同一個實例)--來自百度百科多線程

應用:

單例模式可讓咱們只建立一個對象從而避免了頻繁建立對象致使的內存消耗和垃圾回收。ide

單例模式只容許建立一個對象,所以節省內存,加快對象訪問速度,所以對象須要被公用的場合適合使用,如多個模塊使用同一個數據源鏈接對象等等。如: 1.須要頻繁實例化而後銷燬的對象。 2.建立對象時耗時過多或者耗資源過多,但又常常用到的對象。 3.有狀態的工具類對象。 4.頻繁訪問數據庫或文件的對象。svg

項目中的具體應用:

  1. 封裝一些經常使用的工具類,保證整個應用經常使用的數據統一
  2. 保存一些共享數據在內存中,其餘類隨時能夠讀取。

實現單例模式的原則和過程:

1.單例模式:確保一個類只有一個實例,自行實例化並向系統提供這個實例 2.單例模式分類:餓單例模式(類加載時實例化一個對象給本身的引用),懶單例模式(調用取得實例的方法如getInstance時纔會實例化對象) 3.單例模式要素: a.私有構造方法 b.私有靜態引用指向本身實例 c.以本身實例爲返回值的公有靜態方法

方式:

單例模式有八種方式:

  1. 餓漢式(靜態常量)
  2. 餓漢式(靜態代碼塊)
  3. 懶漢式(線程不安全方式)
  4. 懶漢式(線程安全,同步方法)
  5. 懶漢式(線程不安全,同步代碼塊)
  6. 懶漢式(雙重檢查)
  7. 懶漢式(靜態內部類)
  8. 枚舉實現

2、單例模式代碼實現及分析

2.一、餓漢式(靜態常量)

代碼實現:

/** * 單例模式 * @Author: crush * @Date: 2021-08-06 9:14 * version 1.0 */
public class SingletonTest1 {
    public static void main(String[] args) {
        // 獲取兩次,看獲取到的對象 確實是單例的嗎
        Singleton singleton = Singleton.getInstance();
        Singleton singleton1 = Singleton.getInstance();

        System.out.println(singleton == singleton1);

        System.out.println("singleton hashcode:"+singleton.hashCode());
        System.out.println("singleton1 hashcode:"+singleton1.hashCode());
        /** * 輸出: * true * singleton hashcode:24324022 * singleton1 hashcode:24324022 */
    }
}

/** * 一、餓漢式(靜態常量)代碼實現 */
class Singleton{
    /*** 構造器私有化*/
    private Singleton(){};
    
    /** * 在類的內部建立一個對象實例 隨當前類加載而加載 沒有線程安全問題。 */
    private final static Singleton INSTANCE=new Singleton();
    
    /*** 再提供一個 公有的方法來返回這個靜態常量*/
    public static Singleton getInstance(){
        return INSTANCE;
    }
}
複製代碼

結論及優缺

  1. 優勢:餓漢式(靜態常量方式)它不用擔憂線程安全問題,它自己就是在類的裝載的時候完成實例化的。

  2. 缺點:可是缺點也在這個地方,無論用不用,只要類裝載了,那麼他就會直接完成實例化,不能達到懶加載的效果,若是你從始至終都沒有使用過這個類,那麼就會形成內存的浪費。

    注意:你可能會想類都已經加載了,爲何我還會不用到呢?

    在單例模式中大都調用getInstance方法,getInstance這個靜態方法可讓類加載,那麼一樣的道理,若是這個類中還有其餘的靜態方法,你調用它的時候,一樣會使類進行裝載。

  3. 小結:這種單例方式,其實仍是能夠用的,至少不用擔憂線程同步問題,那種使用特別頻繁的類用這種方式仍是沒啥問題的,除了可能會致使內存浪費

2.二、餓漢式(靜態代碼塊)

/** * 單例模式 2 * * @Author: crush * @Date: 2021-08-06 9:14 * version 1.0 */
public class SingletonTest1 {
    public static void main(String[] args) {
        // 咱們去拿兩次,看獲取到的對象 確實是單例的嗎
        Singleton singleton = Singleton.getInstance();
        Singleton singleton1 = Singleton.getInstance();

        System.out.println(singleton == singleton1);

        System.out.println("singleton hashcode:" + singleton.hashCode());
        System.out.println("singleton1 hashcode:" + singleton1.hashCode());
        /** * 輸出: * true * singleton hashcode:24324022 * singleton1 hashcode:24324022 */
    }
}

/** * 一、餓漢式(靜態代碼塊)代碼實現 */
class Singleton {

    /** * 構造器私有化 */
    private Singleton() {
    }
    /** * 在類的內部建立一個對象實例 隨當前類加載而加載 沒有線程安全問題。*/
    private static Singleton singleton;

    static {
        //改成在靜態代碼塊中 建立單例對象
        singleton = new Singleton();
    }

    /** * 再提供一個 公有的方法來返回這個靜態常量 */
    public static Singleton getInstance() {
        return singleton;
    }
}
複製代碼

結論:這種方式其實和第一種很是相似,只是將類的實例化過程放進靜態代碼塊而已。優缺點同餓漢式(靜態常量)。

2.三、懶漢式(線程不安全)

/** * 單例模式 * * @Author: crush * @Date: 2021-08-06 9:14 * version 1.0 */
public class SingletonTest3 {
    public static void main(String[] args) {

        //懶漢式 線程不安全方式,適合單線程使用
        //===========單線程下是安全的 ,測試代碼和第一種同樣===========
        // =========模擬多線程下=============
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                Singleton instance = Singleton.getInstance();
                System.out.println(instance.hashCode());
            }
        };

        Runnable runnable2 = new Runnable(){
            @Override
            public void run() {
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1.hashCode());
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
        /** * 結果並不惟一, * 可能會出現相同,也有可能不一樣,多測幾回,就能發現是線程不安全的。 * 94433 * 21648409 */
    }
}

/** * 懶漢式 線程不安全方式 */
class Singleton {

    /*** 構造器私有化*/
    private Singleton() {}
    
    /*** 在類的內部建立一個對象實例 隨當前類加載而加載 沒有線程安全問題。*/
    private static Singleton singleton;

    /** * 提供一個公有的方法 * 當使用到這個方法時,纔去建立singleton */
    public static Singleton getInstance() {
        if(singleton==null){
            // 經過在這裏堵賽的方式來模擬多線程
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton= new Singleton();
        }
        return singleton;
    }
}
複製代碼

結論及優缺:

  1. 優勢:起到了懶加載的效果
  2. 缺點:線程不安全,若是在多線程下,第一個線程進入到if(singleton==null)下,但還將來的及執行,第二個線程就緊隨而來也進入了if(singleton==null)下,那麼就會建立多個實例。就不是單例模式了。
  3. 建議:開發不要使用這種方式,線程安全問題不能解決,就是😱。

2.四、懶漢式(線程安全,同步方法)

/** * 單例模式 * * @Author: crush * @Date: 2021-08-06 9:14 * version 1.0 */
public class SingletonTest4 {
    public static void main(String[] args) {

        //懶漢式 線程不安全方式,適合單線程使用
        //===========單線程下 單線程是安全的===========
        // =========模擬多線程下=============
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                Singleton instance = Singleton.getInstance();
                System.out.println(instance.hashCode());
            }
        };

        Runnable runnable2 = new Runnable(){
            @Override
            public void run() {
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1.hashCode());
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
        /** 6902734 6902734 */
    }
}

/** * 二、懶漢式 線程安全方式 */
class Singleton {

    /*** 構造器私有化 */
    private Singleton() {}
    
    /*** 在類的內部建立一個對象實例 隨當前類加載而加載 沒有線程安全問題。*/
    private static Singleton singleton;


    /** * 提供一個公有的方法 * 當使用到這個方法時,纔去建立singleton * 加上 synchronized 關鍵字 解決線程安全問題 */
    public static synchronized Singleton getInstance() {
        if(singleton==null){
            // 經過在這裏堵賽的方式來模擬多線程
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton= new Singleton();
        }
        return singleton;
    }
}
複製代碼

結論及優缺:

  • 其實代碼和懶漢式線程不安全的實現,就是在Singleton getInstance() 上多了一個了synchronized,將這個方法變成了同步方法,解決了線程同步問題。
  • 缺點:可是由於在方法上加了synchronized 關鍵字,致使執行效率的下降。而且以後每次來獲取,都要進行同步,但其實本質上這段代碼執行一次,以後都是retrun 是最佳的,而方法進行同步就大大下降效率拉。
  • 不推薦這種方式,雖然作到線程同步,但效率過低,影響使用。

2.五、懶漢式(線程並不安全的同步代碼塊)

package com.crush.singleton05;

/** * 單例模式 * * @Author: crush * @Date: 2021-08-06 9:14 * version 1.0 */
public class SingletonTest5 {
    public static void main(String[] args) {

        //懶漢式 線程不安全方式,適合單線程使用
        //===========單線程下是安全的,代碼同上===========
        // =========模擬多線程下=============
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Singleton instance = Singleton.getInstance();
                System.out.println(instance.hashCode());
            }
        };

        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1.hashCode());
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
        /** * 若是這樣的話 多線程是並不安全的。 20775718 5987586 */
    }
}

/** * 二、懶漢式 網上有說這是線程安全的問題,也有說不是的 */
class Singleton {

    /** * 構造器私有化*/
    private Singleton() {
    }

    /*** 在類的內部建立一個對象實例 隨當前類加載而加載 沒有線程安全問題。 */
    private static Singleton singleton;

    /** * 提供一個公有的方法 * 當使用到這個方法時,纔去建立singleton * 在同步代碼塊 處添加 synchronized */
    public static Singleton getInstance() {
        if (singleton == null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Singleton.class) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}
複製代碼

結論及優缺:

1)其實本意是對上一種方式作一個優化,想要提升同步效率,改成這種同步代碼塊的方式

2)但實際上,這並不能起到線程同步的做用,跟上一種方式遇到的問題是同樣的。

3)一樣不建議採起這種方式來實現單例模式。

2.六、懶漢式(雙重檢查)

/** * 單例模式 * * @Author: crush * @Date: 2021-08-06 9:14 * version 1.0 */
public class SingletonTest6 {
    public static void main(String[] args) {

        //懶漢式 線程安全方式,適合單、多線程使用
        //===========單線程下是安全的,代碼同上===========
        // =========模擬多線程下=============
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Singleton instance = Singleton.getInstance();
                System.out.println(instance.hashCode());
            }
        };

        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1.hashCode());
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
        /** * 線程安全 * 7739563 * 7739563 */
    }
}

/** * 二、懶漢式 雙重檢查 線程安全 */
class Singleton {

    /*** 構造器私有化*/
    private Singleton() {
    }

    /*** 在類的內部建立一個對象實例 隨當前類加載而加載 沒有線程安全問題。*/
    private static Singleton singleton;


    /** * 提供一個公有的方法 * 當使用到這個方法時,纔去建立singleton * 在同步代碼塊 處添加 synchronized * 雙重檢查 */
    public static Singleton getInstance() {
        if (singleton == null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Singleton.class) {
                if(singleton==null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
複製代碼

結論及優缺:

1)咱們在代碼中進行了兩次if (singleton == null)操做,一次在同步代碼塊外,一次在同步代碼塊內,兩次檢查,保證了線程的安全。

2)這樣的話,同步代碼只要執行一次,singleton也只需實例化一次,既作到了懶加載,又同時保證線程安全,提升了執行效率。

3)結論:懶加載、線程安全、效率較高,固然用這種方式啊。

2.七、懶漢式(靜態內部類)

/** * 單例模式 * * @Author: crush * @Date: 2021-08-06 9:14 * version 1.0 */
public class SingletonTest7 {
    public static void main(String[] args) {

        //懶漢式 線程安全方式,適合單、多線程使用
        //===========單線程下是安全的 和上面同樣的===========
        // =========模擬多線程下=============
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Singleton instance = Singleton.getInstance();
                System.out.println(instance.hashCode());
            }
        };

        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1.hashCode());
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
        /** * 線程安全 * 7739563 * 7739563 */
    }
}

/** * 二、懶漢式 靜態內部類方式 */
class Singleton {

    /*** 構造器私有化*/
    private Singleton() {
    }

    /** * 寫一個靜態內部類,而後在類的內部再寫一個靜態常量 Singleton */
    private static class SingletonInstance {
        private final static Singleton SINGLETON=new Singleton();
    }


    /** * 提供一個公有的方法 * 當使用到這個方法時,纔去加載 SingletonInstance 得到 Singleton 實例 */
    public static Singleton getInstance() {
        return SingletonInstance.SINGLETON;
    }
}
複製代碼

結論及優缺:

1)這種方式一樣不會產生線程同步問題,也是借用JVM的類裝載的機制來保證明例化的時候只有一個線程。

2)靜態內部類SingletonInstanceSingleton被裝載時,並不會當即實例化,而是在須要實例化的時候,調用了getInstance 方法,纔會進行 SingletonInstance類的裝載。

3)類的靜態屬性只會在第一次加載類的時候進行初始化,而在這裏,JVM的類裝載機制幫助咱們保證了線程安全性。

4)小結:避免了線程安全問題、利用了靜態內部類延遲加載(作到懶加載)、效率高,這不更爽了嗎,用起來。

2.八、枚舉類實現

/** * 單例模式 * * @Author: crush * @Date: 2021-08-06 9:14 * version 1.0 */
public class SingletonTest8 {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.SINGLETON;
        Singleton singleton2 = Singleton.SINGLETON;

        System.out.println(singleton1 == singleton2);
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }
}

/** * 二、 枚舉方式 */
enum Singleton {
    SINGLETON;
}
複製代碼

結論及優缺:

  • 沒有多線程問題,效率高,可以防止反序列化從新建立對象,也是推薦使用的方式。

:通常來講,大都採用餓漢式,並竟方便,若是十分在乎資源的話,通常採用靜態內部類。可是一切的使用,都是具體問題具體分析,沒有最好的,只有最適合的。

3、單例在一些源碼中的使用

· 3.一、JDK

像JDK中的 Runtime就是使用了餓漢式單例方式實現 在這裏插入圖片描述

3.二、Mybatis

Mybatis中 ErrorContextThreadLocal基於線程惟一 在這裏插入圖片描述

Spring中也有蠻多哈,沒有一一去找了。👨‍🦲

4、自言自語

你卷我卷,你們卷,何時這條路纔是個頭啊。

有時候也想停下來歇一歇,一直作一個事情,感受挺難堅持的。👩‍💻

你好,若是你正巧看到這篇文章,而且以爲對你有益的話,就給個贊吧,讓我感覺一下分享的喜悅吧,蟹蟹。

如如有寫的有誤的地方,請不嗇賜教!!

一樣如如有存在疑惑的地方,請留言或私信,定會在第一a時間回覆你。

持續更新中

相關文章
相關標籤/搜索