溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java設(shè)計(jì)模式之怎么實(shí)現(xiàn)單例模式

發(fā)布時(shí)間:2022-11-08 09:51:54 來(lái)源:億速云 閱讀:117 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹了java設(shè)計(jì)模式之怎么實(shí)現(xiàn)單例模式的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇java設(shè)計(jì)模式之怎么實(shí)現(xiàn)單例模式文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

單元素的枚舉類型經(jīng)常成為實(shí)現(xiàn) Singleton 的最佳方法 。

什么是單例?就一條基本原則,單例對(duì)象的類只會(huì)被初始化一次。在 Java 中,我們可以說(shuō)在 JVM 中只存在該類的唯一一個(gè)對(duì)象實(shí)例。在 Android 中,我們可以說(shuō)在程序運(yùn)行期間,該類有且僅有一個(gè)對(duì)象實(shí)例。

單例模式的簡(jiǎn)單實(shí)現(xiàn)步驟:

  • 構(gòu)造方法私有,保證無(wú)法從外部通過(guò) new 的方式創(chuàng)建對(duì)象。

  • 對(duì)外提供獲取該類實(shí)例的靜態(tài)方法。

  • 類的內(nèi)部創(chuàng)建該類的對(duì)象,通過(guò)第 2 步的靜態(tài)方法返回。

按照上述步驟寫下你認(rèn)為比較嚴(yán)謹(jǐn)?shù)膯卫J?,然后看看你所寫下的單例能否滿足以下條件:

  • 你的單例按需加載嗎?

  • 你的單例線程安全嗎?涉及到并發(fā)三要素:原子性、可見(jiàn)性、有序性

  • 你的單例暴力反射和序列化安全嗎?


一、餓漢式

//JAVA實(shí)現(xiàn)public class SingleTon {    //第三步創(chuàng)建唯一實(shí)例
    private static SingleTon instance = new SingleTon();    
    //第一步構(gòu)造方法私有
    private SingleTon() {
    }    
    //第二步暴露靜態(tài)方法返回唯一實(shí)例
    public static SingleTon getInstance() {        return instance;
    } 
}//Kotlin實(shí)現(xiàn)object SingleTon

優(yōu)點(diǎn):設(shè)計(jì)簡(jiǎn)單 ,解決了多線程實(shí)例化的問(wèn)題。

缺點(diǎn):在虛擬機(jī)加載SingleTon類的時(shí)候,將會(huì)在初始化階段為類靜態(tài)變量賦值,也就是在虛擬機(jī)加載該類的時(shí)候(此時(shí)可能并沒(méi)有調(diào)用 getInstance 方法)就已經(jīng)調(diào)用了 new SingleTon(); 創(chuàng)建了該對(duì)象的實(shí)例,之后不管這個(gè)實(shí)例對(duì)象用不用,都會(huì)占據(jù)內(nèi)存空間。

二、懶漢式

//JAVA實(shí)現(xiàn)public class SingleTon {    //創(chuàng)建唯一實(shí)例
    private static SingleTon instance = null;    
    private SingleTon() {
    }    
    public static SingleTon getInstance() {        //延遲初始化 在第一次調(diào)用 getInstance 的時(shí)候創(chuàng)建對(duì)象
        if (instance == null) {
            instance = new SingleTon();
        }        return instance;
    } 
}//Kotlin實(shí)現(xiàn)class SingleTon private constructor() {    companion object {        private var instance: SingleTon? = null
            get() {                if (field == null) {
                    field = SingleTon()
                }                return field
            }        fun get(): SingleTon{            return instance!!
        }
    }
}

優(yōu)點(diǎn):設(shè)計(jì)也是比較簡(jiǎn)單的,和餓漢式不同,當(dāng)這個(gè)Singleton被加載的時(shí)候,被static修飾的靜態(tài)變量將會(huì)被初始化為null,這個(gè)時(shí)候并不會(huì)占用內(nèi)存,而是當(dāng)?shù)谝淮握{(diào)用getInstance方法的時(shí)候才會(huì)被初始化實(shí)例對(duì)象,按需創(chuàng)建。

缺點(diǎn):在單線程環(huán)境下是沒(méi)有問(wèn)題的,在多線程環(huán)境下,會(huì)產(chǎn)生線程安全問(wèn)題。在有兩個(gè)線程同時(shí) 運(yùn)行到了 instane == null這個(gè)語(yǔ)句,并且都通過(guò)了,那他們就會(huì)都各自實(shí)例化一個(gè)對(duì)象,這樣就又不是單例了。

如何解決懶漢式在多線程環(huán)境下的多實(shí)例問(wèn)題?

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


    //JAVA實(shí)現(xiàn)public class SingleTon {    
       private static class InnerSingleton{        private static SingleTon singleTon  = new SingleTon();
       }    public SingleTon getInstance(){        return InnerSingleton.singleTon;
       }    
       private SingleTon() {
       }
    }//kotlin實(shí)現(xiàn)class SingleTon private constructor() {
       companion object {        val instance = InnerSingleton.instance
       }    private object InnerSingleton {        val instance = SingleTon()
       }
    }
  • 直接同步方法


    //JAVA實(shí)現(xiàn)public class SingleTon {    //創(chuàng)建唯一實(shí)例
       private static SingleTon instance = null;    
       private SingleTon() {
       }    
       public static synchronized SingleTon getInstance() {        if (instance == null) {
               instance = new SingleTon();
           }        return instance;
       }
    }//Kotlin實(shí)現(xiàn)class SingleTon private constructor() {  companion object {      private var instance: SingleTon? = null
             get() {              if (field == null) {
                     field = SingleTon()
                 }              return field
             }      @Synchronized
         fun get(): SingleTon{          return instance!!
         }
     }
    }
  • 優(yōu)點(diǎn):加鎖只有一個(gè)線程能實(shí)例該對(duì)象,解決了線程安全問(wèn)題。

    缺點(diǎn):對(duì)于靜態(tài)方法而言,synchronized關(guān)鍵字會(huì)鎖住整個(gè) Class,每次調(diào)用getInstance方法都會(huì)線程同步,效率十分低下,而且當(dāng)創(chuàng)建好實(shí)例對(duì)象之后,也就不必繼續(xù)進(jìn)行同步了。

    備注:此處的synchronized保證了操作的原子性和內(nèi)存可見(jiàn)性。

  • 同步代碼塊(雙重檢鎖方式DCL)


    //JAVA實(shí)現(xiàn) public class SingleTon {    //創(chuàng)建唯一實(shí)例
       private static volatile SingleTon instance = null;    
       private SingleTon() {
       }    
       public static SingleTon getInstance() {        if (instance == null) {
               synchronized (SingleTon.class) {  
                   if (instance == null) {
                       instance = new SingleTon();
                   }
               }
           }        return instance;
       }
    }//kotlin實(shí)現(xiàn)class SingleTon private constructor() {    companion object {        val instance: SingleTon by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
               SingleTon()
           }
     }
    }
    或者class SingleTon private constructor() {    companion object {        @Volatile private var instance: SingleTon? = null
           fun getInstance() =
                 instance ?: synchronized(this) {
                     instance ?: SingleTon().also { instance = it }
                 }
     }
    }
  • 優(yōu)點(diǎn):添加了一個(gè)同步代碼塊,在同步代碼塊中去判斷實(shí)例對(duì)象是否存在,如果不存在則去創(chuàng)建,這個(gè)時(shí)候其實(shí)就完全可以解決問(wèn)題了,因?yàn)殡m然是多個(gè)線程去獲取實(shí)例對(duì)象,但是在同一個(gè)時(shí)間也只會(huì)有一個(gè)線程會(huì)進(jìn)入到同步代碼塊,那么這個(gè)時(shí)候創(chuàng)建好對(duì)象之后,其他線程即便再次進(jìn)入同步代碼塊,由于已經(jīng)創(chuàng)建好了實(shí)例對(duì)象,便直接返回即可。但是為什么還要在同步代碼塊的上一步再次去判斷instance為空呢?這個(gè)是由于當(dāng)我們創(chuàng)建好實(shí)例對(duì)象之后,直接去判斷此實(shí)例對(duì)象是否為空,如果不為空,則直接返回就好了,就避免再次進(jìn)去同步代碼塊了,提高了性能。

    缺點(diǎn):無(wú)法避免暴力反射創(chuàng)建對(duì)象。

    備注:此處的volatile發(fā)揮了內(nèi)存可見(jiàn)性及防止指令重排序作用。

三、枚舉實(shí)現(xiàn)單例

public enum SingletonEnum {    INSTANCE;    public static void main(String[] args) {        System.out.println(SingletonEnum.INSTANCE == SingletonEnum.INSTANCE);
    }
}

枚舉實(shí)現(xiàn)單例是最為推薦的一種方法,因?yàn)榫退阃ㄟ^(guò)序列化,反射等也沒(méi)辦法破壞單例性。(關(guān)于Android使用枚舉會(huì)產(chǎn)生性能問(wèn)題的說(shuō)法,這應(yīng)該是Android 2.x系統(tǒng)之前內(nèi)存緊張的時(shí)代了,現(xiàn)在已經(jīng)Android 13了,相信某些場(chǎng)合枚舉所帶來(lái)的便利遠(yuǎn)遠(yuǎn)大于這點(diǎn)所謂的性能影響)

四、如何避免單例模式反射攻擊

以最初的DCL為測(cè)試案例,看看如何進(jìn)行反射攻擊及又如何在一定程度上避免反射攻擊。反射攻擊代碼如下:

 public static void main(String[] args) {

     SingleTon singleton1 = SingleTon.getInstance();
     SingleTon singleton2 = null;

     try {
         Class<SingleTon> clazz = SingleTon.class;
         Constructor<SingleTon> constructor = clazz.getDeclaredConstructor();
         constructor.setAccessible(true);
         singleton2 = constructor.newInstance();
     } catch (Exception e) {
         e.printStackTrace();
     }

     System.out.println("singleton1.hashCode():" + singleton1.hashCode());
     System.out.println("singleton2.hashCode():" + singleton2.hashCode());
 }

執(zhí)行結(jié)果:

 singleton1.hashCode():1296064247
 singleton2.hashCode():1637070917

通過(guò)執(zhí)行結(jié)果發(fā)現(xiàn)通過(guò)反射破壞了單例。 如何保證反射安全呢?只能以暴制暴,當(dāng)已經(jīng)存在實(shí)例的時(shí)候再去調(diào)用構(gòu)造函數(shù)直接拋出異常,對(duì)構(gòu)造函數(shù)做如下修改:

  public class SingleTon {     //創(chuàng)建唯一實(shí)例
     private static volatile SingleTon instance = null;   
     private SingleTon() {         if (instance != null) {             throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");
         }
     }   
     public static SingleTon getInstance() {         if (instance == null) {
           synchronized (SingleTon.class) {   
               if (instance == null) {
                   instance = new SingleTon();
               }
           }
       }       return instance;
     } 
 }

此時(shí)可防御反射攻擊,拋出異常如下:

 java.lang.reflect.InvocationTargetException
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
 at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:45)
 at com.imock.demo.TestUtil.main(TestUtil.java:33)
 Caused by: java.lang.RuntimeException: 單例構(gòu)造器禁止反射調(diào)用
 at com.imock.demo.SingleTon.<init>(SingleTon.java:16)
 ... 6 more Exception in thread "main" java.lang.NullPointerException
 at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:49)
 at com.imock.demo.TestUtil.main(TestUtil.java:33) 
 Process finished with exit code 1

然后我們把上述測(cè)試代碼修改如下(調(diào)換了singleton1的初始化順序)

 public static void main(String[] args) {
     SingleTon singleton2 = null;

     try {
         Class<SingleTon> clazz = SingleTon.class;
         Constructor<SingleTon> constructor = clazz.getDeclaredConstructor();
         constructor.setAccessible(true);
         singleton2 = constructor.newInstance();
     } catch (Exception e) {
         e.printStackTrace();
     }

     System.out.println("singleton2.hashCode():" + singleton2.hashCode());

     SingleTon singleton1 = SingleTon.getInstance(); //調(diào)換了位置,在反射之后執(zhí)行
     System.out.println("singleton1.hashCode():" + singleton1.hashCode());
 }

執(zhí)行結(jié)果:

 singleton2.hashCode():1296064247
 singleton1.hashCode():1637070917

發(fā)現(xiàn)此防御未起到作用。

缺點(diǎn):

  • 如果反射攻擊發(fā)生在正常調(diào)用getInstance之前,每次反射攻擊都可以獲取單例類的一個(gè)實(shí)例,因?yàn)榧词顾接袠?gòu)造器中使用了靜態(tài)成員(instance) ,但單例對(duì)象并沒(méi)有在類的初始化階段被實(shí)例化,所以防御代碼不生效,從而可以通過(guò)構(gòu)造器的反射調(diào)用創(chuàng)建單例類的多個(gè)實(shí)例;

  • 如果反射攻擊發(fā)生在正常調(diào)用之后,防御代碼是可以生效的;

如何避免序列化攻擊?只需要修改反序列化的邏輯就可以了,即重寫 readResolve() 方法,使其返回統(tǒng)一實(shí)例。

   protected Object readResolve() {       return getInstance();
   }

脆弱不堪的單例模式經(jīng)過(guò)重重考驗(yàn),進(jìn)化成了完全體,延遲加載,線程安全,反射及序列化安全。簡(jiǎn)易代碼如下:

  • 餓漢模式


    public class SingleTon {    private static SingleTon instance = new SingleTon();    
       private SingleTon() {        if (instance != null) {              throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");
            }
       }    public static SingleTon getInstance() {        return instance;
       }
    }
  • 靜態(tài)內(nèi)部類


    public class SingleTon {    
       private static class InnerStaticClass{        private static SingleTon singleTon  = new SingleTon();
       }    public SingleTon getInstance(){        return InnerStaticClass.singleTon;
       }    
       private SingleTon() {       if (InnerStaticClass.singleTon != null) {           throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");
          }
       }
    }
  • 懶漢模式


    public class SingleTon {    //創(chuàng)建唯一實(shí)例
       private static SingleTon instance = null;    
       private SingleTon() {        if (instance != null) {              throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");
           }
       }    
       public static SingleTon getInstance() {        //延遲初始化 在第一次調(diào)用 getInstance 的時(shí)候創(chuàng)建對(duì)象
           if (instance == null) {
               instance = new SingleTon();
           }        return instance;
       }
    }
  • 缺點(diǎn):

    • 如果反射攻擊發(fā)生在正常調(diào)用getInstance之前,每次反射攻擊都可以獲取單例類的一個(gè)實(shí)例,因?yàn)榧词顾接袠?gòu)造器中使用了靜態(tài)成員(instance) ,但單例對(duì)象并沒(méi)有在類的初始化階段被實(shí)例化,所以防御代碼不生效,從而可以通過(guò)構(gòu)造器的反射調(diào)用創(chuàng)建單例類的多個(gè)實(shí)例;

    • 如果反射攻擊發(fā)生在正常調(diào)用之后,防御代碼是可以生效的。


(枚舉實(shí)現(xiàn)單例是最為推薦的一種方法,因?yàn)榫退阃ㄟ^(guò)序列化,反射等也沒(méi)辦法破壞單例性,底層實(shí)現(xiàn)比如newInstance方法內(nèi)部判斷枚舉拋異常)

Java可以用來(lái)干什么

Java主要應(yīng)用于:1. web開(kāi)發(fā);2. Android開(kāi)發(fā);3. 客戶端開(kāi)發(fā);4. 網(wǎng)頁(yè)開(kāi)發(fā);5. 企業(yè)級(jí)應(yīng)用開(kāi)發(fā);6. Java大數(shù)據(jù)開(kāi)發(fā);7.游戲開(kāi)發(fā)等。

關(guān)于“java設(shè)計(jì)模式之怎么實(shí)現(xiàn)單例模式”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“java設(shè)計(jì)模式之怎么實(shí)現(xiàn)單例模式”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI