【設計模式】單例模式的八種姿態寫法分析

前言
網上氾濫流傳單例模式的寫法種類,有說7種的,也有說6種的,固然也不排除說5種的,他們說的有錯嗎?其實沒有對與錯,刨根問底,寫法終究是寫法,其本質精髓大致一致!所以徹底不必去追究寫法的多少,有這個時間還不如跟着宜春去網吧偷耳機、去田裏抓青蛙得了,一每天的....java

言歸正傳...單例模式是最經常使用到的設計模式之一,熟悉設計模式的朋友對單例模式絕對不會陌生。同時單例模式也是比較簡單易理解的一種設計模式。面試

@spring

何謂單例模式?

專業術語數據庫

單例模式是一種經常使用的軟件設計模式,其定義是單例對象的類只能容許一個實例存在。許多時候整個系統只須要擁有一個的全局對象,這樣有利於咱們協調系統總體的行爲。好比在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,而後服務進程中的其餘對象再經過這個單例對象獲取這些配置信息。這種方式簡化了在複雜環境下的配置管理。編程

單例模式,簡單的說就是 一個類只能有一個實例,而且在整個項目中都能訪問到這個實例。設計模式

單例模式的優勢

一、在內存中只有一個對象,節省內存空間。
二、避免頻繁的建立銷燬對象,能夠提升性能。
三、避免對共享資源的多重佔用。
四、能夠全局訪問。安全

單例模式實現總體思路流程

首先咱們要清楚單例模式要求類可以有返回對象一個引用(永遠是同一個)和一個得到該實例的方法(必須是靜態方法,一般使用getInstance這個名稱)。服務器

單例模式的常規實現思路大體相同爲如下三個步驟:多線程

一、私有構造方法
二、指向本身實例的私有靜態引用
三、以本身實例爲返回值的靜態的公有的方法

固然也能夠理解爲
一、私有化構造方法,讓外部不能new。
二、本類內部建立對象實例【靜態變量目的是爲了類加載的時候建立實例】
三、提供一個公有的static靜態方法(通常該方法使用getInstance這個名稱),返回實例對象。

將該類的構造方法定義爲私有方法,這樣其餘處的代碼就沒法經過調用該類的構造方法來實例化該類的對象,只有經過該類提供的靜態方法來獲得該類的惟一實例;
在該類內提供一個靜態方法,當咱們調用這個方法時,若是類持有的引用不爲空就返回這個引用,若是類保持的引用爲空就建立該類的實例並將實例的引用賦予該類保持的引用。

單例模式的適用場景

因爲單例模式有不少獨特的優勢,因此是編程中用的比較多的一種設計模式。我總結了一下我所知道的適合使用單例模式的場景:

一、須要頻繁實例化而後銷燬的對象。
二、建立對象時耗時過多或者耗資源過多,但又常常用到的對象。
三、有狀態的工具類對象。
四、頻繁訪問數據庫或文件的對象。

在後面我將會講到JDK中的Runtime類就是使用的餓漢式單例!在Spring MVC框架中的controller 默認是單例模式的!

單例模式的八種姿態寫法

宜春強烈建議:若是是沒有接觸單例模式的讀者朋友強烈建議大家動手敲一遍,不要複製,否則沒效果!

還有一點就是,要真正垂手可得的理解單例模式,JVM的類加載知識是不能少的,否則你只是會敲的層次,啥?不懂類加載?放心,宜春就是要你會,要你理解透徹。

別翻了,這篇文章絕對讓你深入理解java類的加載以及ClassLoader源碼分析【JVM篇二】

其實上面的這篇文章特別重要,上面這篇文章的重要性懂的天然懂,不懂的但願能理解宜春的一片好意,去看一下吧,實在看不懂看不下去在回來看這篇文章就行了,再大不了就把博主一塊兒按在馬桶蓋蓋上....

是否是內心暖暖的?宜春也很少嗶嗶了,直接擼碼走起....

姿態一:餓漢式1(靜態變量)

package singletonPattern;
//餓漢式(靜態變量)

class Singleton{
    //一、私有化構造方法,讓外部不能new
    private Singleton(){

    }
    //二、本類內部建立對象實例【靜態變量目的是爲了類加載的時候建立實例】
    private final static Singleton instance=new Singleton();

    //三、提供一個公有的static靜態方法,返回實例對象
    public static Singleton getInstance(){
        return instance;
    }
}
//如下是測試代碼=====================

public class SingletenDemo1 {
    public static void main(String[] args) {
        Singleton singleton=Singleton.getInstance();
        Singleton singleton2=Singleton.getInstance();
//驗證一:
        System.out.println(singleton==singleton2);
//驗證二:
        System.out.println(singleton.hashCode());
        System.out.println(singleton2.hashCode());
    }
}

//運行結果:
//        true
//        460141958
//        460141958

/*
餓漢式(靜態變量)方法

優勢:寫法簡單,在類加載的時候就完成了實例化,同時也就避免了線程同步問題,所以線程安全
缺點:因爲是在類加載時就完成了實例化,沒有達到懶加載的效果。若是一直沒有使用過這個實例,就形成了內存的浪費!

總結:這種方式基於ClassLoader類加載機制避免了多線程的同步問題,只不過instance屬性在類加載就實例化,在單例模式中大多數都是調用getInstance方法,
     因爲getInstance方法是static靜態的,調用它確定會觸發類加載!可是觸發類加載的緣由有不少,咱們不能保證這個類會經過其餘的方式觸發類加載(好比調用了其餘的static方法)
     這個時候初始化instance就沒有達到lazy loading 懶加載的效果,可能形成內存的浪費!

     餓漢式(靜態變量)這種方式可使用可是會形成內存的浪費!

     */

姿態二:餓漢式2(static靜態代碼塊)

package singletonPattern;
//餓漢式2(static靜態代碼塊)

class Singleton2{
    private Singleton2(){

    }

    private static Singleton2 instance;

    static{ //把建立單例對象的操做放進了static靜態代碼塊中==============
        instance = new Singleton2();
    }

    public static Singleton2 getInstance(){
        return instance;
    }
}
//餓漢式2(static靜態代碼塊)其實和第一種餓漢式(靜態變量)方法差很少,其優缺點一致!
//惟一不一樣的就是把建立單例對象的操做放進了static靜態代碼塊中

姿態三:懶漢式1(線程不安全)

package singletonPattern;
//懶漢式1(線程不安全)
class Singleton3{
    private Singleton3(){

    }

    private static Singleton3 instance;

    public static Singleton3 getInstance(){
        if(instance == null){
            instance=new Singleton3();
        }
        return instance;
    }
}
/*
懶漢式(線程不安全)的這種方式起到了懶加載的效果,但只能在單線程下使用。
若是在多線程下,一個線程進入了if(singleton==null)判斷語句塊,還沒執行產生實例的句子,另外一個線程
又進來了,這時會產生多個實例,因此不安全。

結語:懶漢式(線程不安全)在實際開發中,不要使用這種方式!!存在潛在危險
*/

姿態四:懶漢式2(線程安全)

package singletonPattern;
//懶漢式2(線程安全)
class Singleton4{
    private Singleton4(){

    }

    private static Singleton4 instance;

    public static synchronized Singleton4 getInstance(){
        if(instance == null){
            instance=new Singleton4();
        }
        return instance;
    }
}

/*
懶漢式2(線程安全)方式

優勢:線程安全
缺點:效率過低,每次調用getInstance方法都要進行同步

結語:懶漢式2(線程安全)方式在開發中不推薦使用,主要是效率過低了*/

姿態五:餓漢式2(static靜態代碼塊)

package singletonPattern;
//懶漢式3 同步代碼塊(線程安全) 可是不知足單例,在多線程下依舊會有多個實例
class Singleton5{
    private Singleton5(){

    }

    private static Singleton5 instance;

    public static  Singleton5 getInstance(){
        if(instance == null){   //多線程狀況下可能多個線程進入這個if塊
            synchronized (Singleton5.class){  //到這裏只會一個一個建立實例,雖然安全,可是就再也不是單例了
                instance=new Singleton5();
            }
        }
        return instance;
    }
}
/*
懶漢式3 同步代碼塊(線程安全) 可是不知足單例,依舊會有多個實例

結語:懶漢式3 同步代碼塊(線程安全)方式在開發中不使用 ,實際上這個單例設計的有點搞笑*/

姿態六:雙重檢查單例

package singletonPattern;
//雙重檢查應用實例方式
class Singleton6{
    private Singleton6(){}

    private static volatile Singleton6 singleton;

    public static Singleton6 getInstance(){
        if(singleton==null){
            synchronized(Singleton6.class){
                if(singleton == null){
                    singleton= new Singleton6();
                }
            }
        }
        return singleton;
    }
}
/*
雙重檢查應用實例方式:

線程安全、延遲加載、效率較高

結語:開發中推薦使用!
*/

這個時候博主就得嗶嗶幾句了,細心的童鞋會發現有一個Volatile關鍵字,完了,沒見過,小白童鞋慌了!

Volatile 變量具備 synchronized 的可見性特性,可是不具有原子特性。這就是說線程可以自動發現 volatile 變量的最新值。

這種實現方式既能夠實現線程安全地建立實例,而又不會對性能形成太大的影響。它只是第一次建立實例的時候同步,之後就不須要同步了,從而加快了運行速度。

姿態七:靜態內部類單例

package singletonPattern;
//static靜態內部類單例

class Singleton7{
    private Singleton7(){}

    private static volatile Singleton7 instance;

    //寫一個static靜態內部類,給該類添加一個static靜態instance屬性
    private static class SingletonInstance{
        private static final Singleton7 SINGLETON_7=new Singleton7();
    }

    //
    public static synchronized Singleton7 getInstence(){
        return SingletonInstance.SINGLETON_7;
    }
}
/*
靜態內部類單例方式
        一、這種方式採用了類加載機制來保證初始化實例時只有一個線程
        二、巧妙的將實例化Singleton操做放進getInstance方法中,getInstance方法返回靜態內部類中實例化好的Singleton
        三、類的靜態屬性只會在第一次加載類的時候初始化,也就是隻會初始化一次,在這裏,JVM幫咱們保證了線程的安全,類在初始化時,別的線程沒法進入。
       
        優勢:線程安全、利用靜態內部類特色實現延遲加載、效率高
        開發中推薦使用這種靜態內部類單例方式!

static靜態內部特色:
一、外部類加載不會致使內部類加載,保證了其懶加載
*/

這個單例,宜春就不得不嗶嗶兩句了,要清楚這個單例,必需要明白static靜態內部特色,也就是外部類加載不會致使內部類加載!

姿態八:餓漢式2(static靜態代碼塊)

package singletonPattern;
//使用枚舉

import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader;

enum Singleton8{
    INSTANCE;
    public void methodName(){
        System.out.println("測試數據");
    }
}
/*

枚舉方式的枚舉:
推薦寫法,簡單高效。充分利用枚舉類的特性,只定義了一個實例,且枚舉類是自然支持多線程的。
藉助JDK1.5中添加的枚舉來實現單例模式優勢:
         一、不只能避免多線程同步問題 
         二、還能防止反序列化從新建立新的對象

枚舉方式單例是由Effective java做者Josh Bloch提倡的,結語:推薦使用!
*/

固然也能夠測試一下

public class SingletonDemo8 {
    public static void main(String[] args) {
        Singleton8 instance = Singleton8.INSTANCE;
        Singleton8 instance2 = Singleton8.INSTANCE;
        System.out.println(instance==instance2);

        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());

        instance.methodName();
    }
}

運行結果:

true
460141958
460141958
測試數據

屬實沒毛病!

JDK源碼中單例模式的應用

先來看一段Runtime 的源碼吧,並分析一下其使用的是種單例模式!

/**
 * Every Java application has a single instance of class
 * <code>Runtime</code> that allows the application to interface with
 * the environment in which the application is running. The current
 * runtime can be obtained from the <code>getRuntime</code> method.
 * <p>
 * An application cannot create its own instance of this class.
 *
 * @author  unascribed
 * @see     java.lang.Runtime#getRuntime()
 * @since   JDK1.0
 */
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

這應該不難看出吧!若是看不出的話只能說明你真的尚未理解單例模式,我其實想說單例模式實際上是23種設計模式中最簡單的一個,只是寫法比較多而已!同時面試官通常都會問單例模式,它已是很基礎的了,問的稍微有點水平就是問你單例模式在JDK中哪裏運用到了,顯然JDK中的Runtime其實它使用的就是餓漢式單例!正如註釋所說,每個java應用程序都有一個Runtime實例。Runtime的單例模式是採用餓漢模式建立的,意思是當你加載這個類文件時,這個實例就已經存在了。

Runtime類能夠取得JVM系統信息,或者使用gc()方法釋放掉垃圾空間,還可使用此類運行本機的程序。

==還有就是spring Mvc 中的controller 默認是單例模式的,解析。==

單例模式總結

一、餓漢式(靜態變量)這種方式可使用,可是沒有達到 lazy loading 懶加載的效果會形成內存的浪費!開發中不建議使用。
二、餓漢式(static靜態代碼塊)其實和第一種餓漢式(靜態變量)方法差很少,其優缺點一致!惟一不一樣的就是把建立單例對象的操做放進了static靜態代碼塊中
三、懶漢式(線程不安全)起到了懶加載的效果,但只能在單線程下使用。在實際開發中,不要使用這種方式!!!
四、懶漢式2(線程安全)方式線程安全可是效率過低,每次調用getInstance方法都要進行同步。因此在開發中不推薦使用。 五、懶漢式3
同步代碼塊(線程安全)方式在開發中不使用 ,實際上這個設計有點搞笑哈哈。
六、雙重檢查應用實例方式,線程安全、延遲加載、效率較高。所以開發中推薦使用!
七、靜態內部類單例方式線程安全、利用靜態內部類特色實現延遲加載、效率高。 開發中推薦使用這種靜態內部類單例方式!
八、藉助JDK1.5中添加的枚舉來實現單例模式不只能避免多線程同步問題還能防止反序列化從新建立新的對象。枚舉方式單例是由Effective java做者Josh Bloch提倡的,開發中推薦使用!

單例模式必須考慮到在多線程的應用場合下的使用,畢竟如今的服務器基本上都是多核的了。

若是本文對你有一點點幫助,那麼請點個讚唄,謝謝~

最後,如有不足或者不正之處,歡迎指正批評,感激涕零!若是有疑問歡迎留言,絕對第一時間回覆!

歡迎各位關注個人公衆號,一塊兒探討技術,嚮往技術,追求技術,說好了來了就是盆友喔...

在這裏插入圖片描述

相關文章
相關標籤/搜索