單例設(shè)計(jì)模式

單例設(shè)計(jì)模式的意思就是,確保一個(gè)類(lèi)只能有一個(gè)對(duì)象。任何別的方法什么的對(duì)該類(lèi)的操作全是對(duì)這一個(gè)對(duì)象的操作。如同我們電腦上的回收站。無(wú)論那個(gè)盤(pán)都有一個(gè)回收站的空間。但都是同一個(gè)??梢栽诨厥照纠锶珓h除了。Jvm里面的GC也是一個(gè)意思。

單例設(shè)計(jì)模式有兩種情況。

一、懶漢式

  1. 聲明一個(gè)私有的靜態(tài)變量
  2. 構(gòu)造器私有化
  3. 創(chuàng)建一個(gè)對(duì)外的公有靜態(tài)方法訪(fǎng)問(wèn)該變量,如果變量不存在,創(chuàng)建該對(duì)象。如果存在則返回這個(gè)私有的靜態(tài)變量。這樣就能保證外部在使用該類(lèi)的對(duì)象時(shí)只會(huì)用同一個(gè)。

基本的懶漢式

class Jvm{
    //聲明一個(gè)私有的靜態(tài)變量
    private static Jvm instance;
    //構(gòu)造器私有化
    private Jvm(){
    }
    //創(chuàng)建一個(gè)對(duì)外的公有靜態(tài)方法訪(fǎng)問(wèn)該變量,如果變量不存在,創(chuàng)建該對(duì)象
    public static Jvm getJvm(){
        
        if(null==instance){
            instance=new Jvm();
        }
        
        return instance;
    }
}

這樣在執(zhí)行下列語(yǔ)句

Jvm jvm1=Jvm.getJvm();
Jvm jvm2=Jvm.getJvm();
System.out.println(jvm1);
System.out.println(jvm2);

他們得到的對(duì)象都是一樣的了。地址一樣即為同一個(gè)對(duì)象。

synTest.Jvm@15db9742
synTest.Jvm@15db9742

但是這樣的懶漢式卻是不安全的。如果是多線(xiàn)程來(lái)使用這個(gè)類(lèi),就會(huì)存在延遲??赡軙?huì)產(chǎn)生多個(gè)對(duì)象。
這里我們放大他的延遲時(shí)間更容易的得到錯(cuò)誤的結(jié)果。
修改上面的Jvm類(lèi)。給他設(shè)置一個(gè)延遲時(shí)間

class Jvm1{
    private static Jvm1 instance;

    private Jvm1() {
    }
    
    public static Jvm1 getJvm(long time) {
        if (null == instance) {
            try {
                Thread.sleep(time); //這里由外部給定延遲時(shí)間
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new Jvm1();
        }
        return instance;
    }
}

創(chuàng)建使用這個(gè)Jvm1的線(xiàn)程

class JvmThread extends Thread{
    private long time;
    
    public JvmThread() {
    }
    //為方便查看,這里構(gòu)造器也傳個(gè)name,給這個(gè)線(xiàn)程設(shè)置名字。
    public JvmThread(long time,String name){
        this.time=time;
        //父類(lèi)繼承了私有的name,直接設(shè)置即可
        super.setName(name);
    }
    
    public void run() {
        System.out.println(Thread.currentThread().getName()+"創(chuàng)建了:"+Jvm1.getJvm(this.time));
        //這里直接在線(xiàn)程里面輸出地址
    }
}

執(zhí)行下列語(yǔ)句

JvmThread jth1=new JvmThread(500,"a");
JvmThread jth2=new JvmThread(1000,"b");
jth1.start();
jth2.start();

上面兩個(gè)線(xiàn)程都使用了Jvm1類(lèi)。構(gòu)造了。按照單例模式的要求,對(duì)象地址應(yīng)該是一樣的。但結(jié)果如下

a創(chuàng)建了:synTest.Jvm1@43e8772d
b創(chuàng)建了:synTest.Jvm1@20b708e4

地址卻不一樣,下面分析

分析

所以這里就需要用到同步措施,使線(xiàn)程變得安全。
比較簡(jiǎn)單的方法是直接使用同步方法,或同步塊

class Jvm1{
    private static Jvm1 instance;

    private Jvm1() {
    }
    //這個(gè)方法改成同步方法即可
    public static synchronized Jvm1 getJvm1(long time) {
        if (null == instance) {
            try {
                Thread.sleep(time); //這里由外部給定延遲時(shí)間
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new Jvm1();
        }
        return instance;
    }
}

或者同步塊

public static Jvm1 getJvm1(long time) {
        synchronized(Jvm1.class){
            if (null == instance) {
                try {
                    Thread.sleep(time); //這里由外部給定延遲時(shí)間
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                instance = new Jvm1();
            }   
            return instance;
        }
    }

但是這樣子設(shè)置會(huì)出現(xiàn)一個(gè)問(wèn)題,每一個(gè)線(xiàn)程調(diào)用這個(gè)方法都得等待,即使已經(jīng)有對(duì)象了,還需要等待。
稍作改進(jìn),人稱(chēng)double check

public static Jvm1 getJvm1(long time) {
        if(null==instance){
            synchronized(Jvm1.class){
                if (null == instance) {
                    try {
                        Thread.sleep(time); //這里由外部給定延遲時(shí)間
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Jvm1();
                }   
            }
        }
    return instance;
    }

這樣子,如果線(xiàn)程調(diào)用時(shí),已經(jīng)有對(duì)象的時(shí)候,就可以直接返回啦。不需要進(jìn)去等待。而如果多個(gè)線(xiàn)程同時(shí)在沒(méi)有對(duì)象時(shí)調(diào)用。也會(huì)進(jìn)入那個(gè)synchronized塊。不會(huì)生成多個(gè)對(duì)象。
<br />
二、餓漢式
1.構(gòu)造器私有化
2.聲明私有的靜態(tài)屬性,同時(shí)創(chuàng)建對(duì)象
3.對(duì)外提供訪(fǎng)問(wèn)屬性的靜態(tài)方法

餓漢式由于對(duì)象直接構(gòu)建了,所以調(diào)用都是調(diào)用那個(gè)對(duì)象,不存在線(xiàn)程安全問(wèn)題了。

餓漢式也有普通的和完善的。

基本餓漢式

class MyJvm{
    //聲明私有的靜態(tài)變量,同時(shí)構(gòu)建對(duì)象
    private static MyJvm instance=new MyJvm();
    
    //構(gòu)造器私有
    private MyJvm() {
    }
    
    public static MyJvm getMyJvm(){
        return instance;
    }
}

這里有一點(diǎn)不好的就是,在聲明MyJvm時(shí)候,就構(gòu)造了他的對(duì)象。如果MyJvm里面還有其他的靜態(tài)方法的話(huà),并且也只使用其他方法,那么那個(gè)對(duì)象的必要性就沒(méi)有了。所以做如下改進(jìn)。

class MyJvm2{
    //這里定義一個(gè)靜態(tài)內(nèi)部類(lèi)。類(lèi)是在調(diào)用的時(shí)候才會(huì)加載的。
    //當(dāng)調(diào)用了下面的getMyJvm2方法時(shí),才會(huì)用到MyJvm2Holder類(lèi),才會(huì)加載這個(gè)類(lèi)。就會(huì)有那個(gè)對(duì)象了。
    private static class MyJvm2Holder{
        private static MyJvm2 instance=new MyJvm2();
    }
    
    private MyJvm2() {
    }
    
    public static MyJvm2 getMyJvm2(){
        return MyJvm2Holder.instance;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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