設計模式之單例模式

前言

本篇文章主要介紹的是設計模式中的單例模式的實現(xiàn)方式。

什么是設計模式

設計模式其實就是前輩們長時間的試驗和錯誤總結出來的,針對軟件開發(fā)過程中面臨的一般問題的解決方案。

設計模式分類

根據(jù)其目的(模式是用來做什么的)可分為創(chuàng)建型,結構型和行為型三種:
? 創(chuàng)建型模式主要用于創(chuàng)建對象。
? 結構型模式主要用于處理類或對象的組合。
? 行為型模式主要用于描述對類或對象怎樣交互和怎樣分配職責。

單例模式

單例模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。這種模式保證一個系統(tǒng)中的某個類只有一個能夠被外界訪問的實例。

單例模式的使用場景

在程序中比較常用的是數(shù)據(jù)庫連接池、線程池、日志對象等等。

單例模式的實現(xiàn)

單例模式的實現(xiàn)有5種方式,分別是懶漢式、餓漢式、雙重鎖、靜態(tài)內(nèi)部類、枚舉。

1.懶漢式

這種方式是最基本的實現(xiàn)方式,但是不支持多線程,線程不安全。

實現(xiàn):定義一個私有的構造方法,定義一個該類靜態(tài)私有的變量,然后定義一個公共的靜態(tài)方法,在靜態(tài)方法內(nèi)對變量的值進行空判斷,不為空直接返回,如果為空重新構建并賦值給改該變量。

public class Singleton {
    private static Singleton singleton;
    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

測試:

public class SingletonTest {
    public static void main(String[] args) {
        System.out.println(Singleton.getInstance()==Singleton.getInstance());
    }
}

這里輸出的是true,可以看到兩次獲取的實例其實是同一個。

采用多線程方式:

public class SingletonTest {
    public static void main(String[] args) {
        new Thread(new SingletonThread()).start();
        new Thread(new SingletonThread()).start();
    }
}
class SingletonThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Singleton.getInstance().hashCode());
    }
}

此時兩次輸出的哈希值時而相同時而不同,出現(xiàn)線程不安全問題。

這種方式可以在靜態(tài)方法的方法聲明上加synchronized關鍵字來確保線程安全,但是效率低下。

2.餓漢式

這種方式?jīng)]有加鎖,所以效率會提高。雖然沒有加鎖,但是也是線程安全的,這是因為它在類加載時就初始化了,而一個類在整個生命周期中只會被加載一次,因此該單例類只會創(chuàng)建一個實例。所以餓漢式天生就是線程安全的。但也正是因為它在類加載的時候就初始化了,會一直占用內(nèi)存,導致內(nèi)存浪費。

定義一個私有的構造方法,并將自身的實例對象設置為一個靜態(tài)私有屬性,然后通過公共的靜態(tài)方法調用返回實例。

public class Singleton {
    private static Singleton singleton  = new Singleton();
    private Singleton() {}

    public static Singleton getInstance() {
        return singleton;
    }
}

測試:和懶漢式的測試代碼一樣,普通方式獲取的兩個實例是同一個,輸出true,多線程方式獲取的哈希值是一樣的。

3.雙重鎖

定義一個私有構造方法,通過volatile定義靜態(tài)私有變量,保證了該變量的可見性,然后定義一個共有的靜態(tài)方法,第一次對該對象實例化時與否判斷,不為空直接返回,提升效率;然后使用synchronized 進行同步代碼塊,防止對象未初始化時,在多線程訪問該對象在第一次創(chuàng)建后,再次重復的被創(chuàng)建;然后第二次對該對象實例化時與否判斷,如果未初始化,則初始化,否則直接返回該實例。

第一次判空是為了提升效率,只有第一次實例化的時候需要加鎖,而不是每次請求都加鎖
第二次是為了進行同步,避免多線程問題。

public class Singleton {
    private volatile static Singleton singleton;
    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) { //#1
            synchronized (Singleton.class){ //#2
                if (singleton == null) { //#3
                    singleton = new Singleton(); //#4
                    System.out.println(Thread.currentThread().getName() + ": singleton is initalized...");//#5.1
                } else {
                    System.out.println(Thread.currentThread().getName() + ": singleton is not null now...");//#5.2
                }
            }
        }
        return singleton;
    }
}

雙重鎖這種方式實現(xiàn)單例的關鍵點在于兩次判空、加鎖、以及volatile關鍵字,這里解釋一下volatile關鍵字在這種方式實現(xiàn)單例起到的作用。

volatile有兩個特性
可見性:證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
有序性:禁止進行指令重排序。

假設不加volatile關鍵字,這段代碼可能輸出的是
thread1: uniqueInstance is initalized...
thread2: uniqueInstance is initalized...

過程分析:
1.thread1進入#1,獲取到singleton為空,此時thread1讓出CPU資源給thread2,thread1進入#1,卻在#2外等待。
2.thread2進入#1,獲取到singleton為空,此時thread2讓出CPU資源給thread1,
thread2進入#1,卻在#2外等待。
3.thread1會依次執(zhí)行#2,#3,#4,#5.1,最終在thread2里面實例化了singleton。thread1執(zhí)行完畢讓出CPU資源給thread2。
4.thread2接著#1跑下去,跑到#3的時候,由于#1里面拿到的singleton還是空(并沒有及時從thread1里面拿到最新的),所以thread2仍然會執(zhí)行#4,#5.1
5.最后在thread1和thread2都實例化了singleton。

這樣的話,singleton實例化了兩次。而volatile關鍵字修飾變量的作用就是讓第四步中thread2及時拿到thread1更新的的singleton,使它最后執(zhí)行#5.2,這里利用的就是可見性。

volatile使singleton重排序被禁止,所有的寫(write)操作都將發(fā)生在讀(read)操作之前。也就確保了thread1的實例化是發(fā)生在thread2第二次判空之前的。

當然,這只是一種假設的情況,沒有重現(xiàn)過,太難模擬了,但是確實存在。

4.靜態(tài)內(nèi)部類

這種方式也是利用了類加載機制,只不過它不像餓漢式一樣,Singleton類被加載就實例化,這樣就沒有達到懶加載的效果。外部類加載時并不需要立即加載內(nèi)部類,內(nèi)部類不被加載則不去初始化instance,因此不占內(nèi)存。而靜態(tài)內(nèi)部類實現(xiàn)單例保證線程安全,是由JVM決定的。

public class Singleton {
    private Singleton() {}

    private static class SingletonInner {
        private static Singleton singleton = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInner.singleton;
    }
}

5.枚舉

枚舉是JDK1.5之后的特性。無償提供序列化機制,防止多次實例化。

public enum Singleton {  
    INSTANCE;  
}

測試:輸出的哈希值都是一樣的。

public class SingletonTest {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new SingletonThread()).start();
        }

    }
}

class SingletonThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Singleton.INSTANCE.hashCode());
    }
}

PS:第2.3.4種測試代碼和第一種是一樣的。

總結:
一般情況下使用餓漢式;如果要求實現(xiàn)懶加載,則使用靜態(tài)內(nèi)部類;如果涉及到反序列化創(chuàng)建對象時,可以嘗試使用枚舉方式。

CSDN:https://blog.csdn.net/qq_27682773
簡書:http://www.itdecent.cn/u/e99381e6886e
博客園:https://www.cnblogs.com/lixianguo

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 單例設計模式全解析 在學習設計模式時,單例設計模式應該是學習的第一個設計模式,單例設計模式也是“公認”最簡單的設計...
    WekingZhang閱讀 409評論 0 1
  • 聲明:原創(chuàng)作品,轉載請注明出處http://www.itdecent.cn/p/b99e870f4ce0 有的時...
    蛇發(fā)女妖閱讀 2,965評論 9 24
  • 一.什么是單例模式 單例模式的定義:確保一個類只有一個實例,并提供一個訪問他的全局訪問點。單例模式是幾個設計模式中...
    Geeks_Liu閱讀 2,330評論 0 10
  • 單例模式,顧名思義,指的是一個類只存在一個實例。 那么,如何保證某一個類只存在一個實例呢?對象的創(chuàng)建是通過類的構造...
    哇哇哇one閱讀 353評論 0 2
  • 親愛的兜兒, 又到周五,謝謝你的翻轉課堂,才成全了我們集書法攝影及心理按摩于一體的美好時光。我們的認真可以在凝神聚...
    蔡新花閱讀 136評論 0 0

友情鏈接更多精彩內(nèi)容