中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java設計模式之怎么實現單例模式

發布時間:2022-11-08 09:51:54 來源:億速云 閱讀:126 作者:iii 欄目:編程語言

這篇文章主要介紹了java設計模式之怎么實現單例模式的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇java設計模式之怎么實現單例模式文章都會有所收獲,下面我們一起來看看吧。

單元素的枚舉類型經常成為實現 Singleton 的最佳方法 。

什么是單例?就一條基本原則,單例對象的類只會被初始化一次。在 Java 中,我們可以說在 JVM 中只存在該類的唯一一個對象實例。在 Android 中,我們可以說在程序運行期間,該類有且僅有一個對象實例。

單例模式的簡單實現步驟:

  • 構造方法私有,保證無法從外部通過 new 的方式創建對象。

  • 對外提供獲取該類實例的靜態方法。

  • 類的內部創建該類的對象,通過第 2 步的靜態方法返回。

按照上述步驟寫下你認為比較嚴謹的單例模式,然后看看你所寫下的單例能否滿足以下條件:

  • 你的單例按需加載嗎?

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

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


一、餓漢式

//JAVA實現public class SingleTon {    //第三步創建唯一實例
    private static SingleTon instance = new SingleTon();    
    //第一步構造方法私有
    private SingleTon() {
    }    
    //第二步暴露靜態方法返回唯一實例
    public static SingleTon getInstance() {        return instance;
    } 
}//Kotlin實現object SingleTon

優點:設計簡單 ,解決了多線程實例化的問題。

缺點:在虛擬機加載SingleTon類的時候,將會在初始化階段為類靜態變量賦值,也就是在虛擬機加載該類的時候(此時可能并沒有調用 getInstance 方法)就已經調用了 new SingleTon(); 創建了該對象的實例,之后不管這個實例對象用不用,都會占據內存空間。

二、懶漢式

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

優點:設計也是比較簡單的,和餓漢式不同,當這個Singleton被加載的時候,被static修飾的靜態變量將會被初始化為null,這個時候并不會占用內存,而是當第一次調用getInstance方法的時候才會被初始化實例對象,按需創建。

缺點:在單線程環境下是沒有問題的,在多線程環境下,會產生線程安全問題。在有兩個線程同時 運行到了 instane == null這個語句,并且都通過了,那他們就會都各自實例化一個對象,這樣就又不是單例了。

如何解決懶漢式在多線程環境下的多實例問題?

  • 靜態內部類


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


    //JAVA實現public class SingleTon {    //創建唯一實例
       private static SingleTon instance = null;    
       private SingleTon() {
       }    
       public static synchronized SingleTon getInstance() {        if (instance == null) {
               instance = new SingleTon();
           }        return instance;
       }
    }//Kotlin實現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!!
         }
     }
    }
  • 優點:加鎖只有一個線程能實例該對象,解決了線程安全問題。

    缺點:對于靜態方法而言,synchronized關鍵字會鎖住整個 Class,每次調用getInstance方法都會線程同步,效率十分低下,而且當創建好實例對象之后,也就不必繼續進行同步了。

    備注:此處的synchronized保證了操作的原子性和內存可見性。

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


    //JAVA實現 public class SingleTon {    //創建唯一實例
       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實現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 }
                 }
     }
    }
  • 優點:添加了一個同步代碼塊,在同步代碼塊中去判斷實例對象是否存在,如果不存在則去創建,這個時候其實就完全可以解決問題了,因為雖然是多個線程去獲取實例對象,但是在同一個時間也只會有一個線程會進入到同步代碼塊,那么這個時候創建好對象之后,其他線程即便再次進入同步代碼塊,由于已經創建好了實例對象,便直接返回即可。但是為什么還要在同步代碼塊的上一步再次去判斷instance為空呢?這個是由于當我們創建好實例對象之后,直接去判斷此實例對象是否為空,如果不為空,則直接返回就好了,就避免再次進去同步代碼塊了,提高了性能。

    缺點:無法避免暴力反射創建對象。

    備注:此處的volatile發揮了內存可見性及防止指令重排序作用。

三、枚舉實現單例

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

枚舉實現單例是最為推薦的一種方法,因為就算通過序列化,反射等也沒辦法破壞單例性。(關于Android使用枚舉會產生性能問題的說法,這應該是Android 2.x系統之前內存緊張的時代了,現在已經Android 13了,相信某些場合枚舉所帶來的便利遠遠大于這點所謂的性能影響)

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

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

 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());
 }

執行結果:

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

通過執行結果發現通過反射破壞了單例。 如何保證反射安全呢?只能以暴制暴,當已經存在實例的時候再去調用構造函數直接拋出異常,對構造函數做如下修改:

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

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

 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: 單例構造器禁止反射調用
 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

然后我們把上述測試代碼修改如下(調換了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(); //調換了位置,在反射之后執行
     System.out.println("singleton1.hashCode():" + singleton1.hashCode());
 }

執行結果:

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

發現此防御未起到作用。

缺點:

  • 如果反射攻擊發生在正常調用getInstance之前,每次反射攻擊都可以獲取單例類的一個實例,因為即使私有構造器中使用了靜態成員(instance) ,但單例對象并沒有在類的初始化階段被實例化,所以防御代碼不生效,從而可以通過構造器的反射調用創建單例類的多個實例;

  • 如果反射攻擊發生在正常調用之后,防御代碼是可以生效的;

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

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

脆弱不堪的單例模式經過重重考驗,進化成了完全體,延遲加載,線程安全,反射及序列化安全。簡易代碼如下:

  • 餓漢模式


    public class SingleTon {    private static SingleTon instance = new SingleTon();    
       private SingleTon() {        if (instance != null) {              throw new RuntimeException("單例構造器禁止反射調用");
            }
       }    public static SingleTon getInstance() {        return instance;
       }
    }
  • 靜態內部類


    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("單例構造器禁止反射調用");
          }
       }
    }
  • 懶漢模式


    public class SingleTon {    //創建唯一實例
       private static SingleTon instance = null;    
       private SingleTon() {        if (instance != null) {              throw new RuntimeException("單例構造器禁止反射調用");
           }
       }    
       public static SingleTon getInstance() {        //延遲初始化 在第一次調用 getInstance 的時候創建對象
           if (instance == null) {
               instance = new SingleTon();
           }        return instance;
       }
    }
  • 缺點:

    • 如果反射攻擊發生在正常調用getInstance之前,每次反射攻擊都可以獲取單例類的一個實例,因為即使私有構造器中使用了靜態成員(instance) ,但單例對象并沒有在類的初始化階段被實例化,所以防御代碼不生效,從而可以通過構造器的反射調用創建單例類的多個實例;

    • 如果反射攻擊發生在正常調用之后,防御代碼是可以生效的。


(枚舉實現單例是最為推薦的一種方法,因為就算通過序列化,反射等也沒辦法破壞單例性,底層實現比如newInstance方法內部判斷枚舉拋異常)

Java可以用來干什么

Java主要應用于:1. web開發;2. Android開發;3. 客戶端開發;4. 網頁開發;5. 企業級應用開發;6. Java大數據開發;7.游戲開發等。

關于“java設計模式之怎么實現單例模式”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“java設計模式之怎么實現單例模式”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

布尔津县| 东城区| 炎陵县| 加查县| 达孜县| 蒙自县| 钟山县| 洛阳市| 文安县| 巩留县| 慈利县| 兴安盟| 汶川县| 温宿县| 云霄县| 元阳县| 手游| 汝南县| 环江| 大港区| 辽宁省| 睢宁县| 合肥市| 桐乡市| 神池县| 浮山县| 财经| 西宁市| 志丹县| 万山特区| 怀集县| 高淳县| 浦东新区| 治多县| 大新县| 灌南县| 台北市| 石门县| 呼伦贝尔市| 渝北区| 大石桥市|