欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java超詳細(xì)講解設(shè)計模式之一的單例模式

 更新時間:2022年03月26日 16:05:14   作者:〖雪月清〗  
單例模式(Singleton Pattern)是 Java 中最簡單的設(shè)計模式之一。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式

單例模式

單例模式顧名思義就是單一的實例,涉及到一個單一的類,該類負(fù)責(zé)創(chuàng)建自己的對象,同時確保只有一個對象被創(chuàng)建,并且提供一種可以訪問這個對象的方式,可以直接訪問,不需要實例化該類的對象。

單例模式的特點:

1.單例類只能有一個實例

2.這個實例必須由單例類自己創(chuàng)建

3.單例類需要提供給外界訪問這個實例

單例模式的作用:

單例模式主要為了保證在Java應(yīng)用程序中,一個類只有一個實例存在。

1.單例模式的結(jié)構(gòu)

單例模式主要有以下角色:

  • 單例類

只能創(chuàng)建一個實例的類

  • 訪問類

測試類,就是使用單例類的類

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

2.1餓漢式

餓漢式:類加載時創(chuàng)建該單實例類對象

1.餓漢式-方式1 靜態(tài)成員變量

創(chuàng)建 餓漢式靜態(tài)成員變量 單例類

public class Demo1 {
 
    /**
     *私有構(gòu)造方法  讓外界不能創(chuàng)建該類對象
     */
    private Demo1(){}

    /**
     * 在類中創(chuàng)建該本類對象 static是由于外界獲取該類對象的方法getInstance()是 static
     * 這個對象instance就是靜態(tài)成員變量
     */
    private static Demo1 instance = new Demo1();

    /**
     * 提供一個公共的訪問方式,讓外界可以獲取該類的對象 static是因為外界不需要創(chuàng)建對象,直接通過類訪問
     */
    public static Demo1 getInstance(){
        return instance;
    }
}

創(chuàng)建 餓漢式靜態(tài)成員變量 測試類(訪問類)

public class Test1 {
    public static void main(String[] args) {
      //創(chuàng)建demo1類的對象 這個時候就無法通過new創(chuàng)建了,因為demo1的構(gòu)造方法是私有的
        Demo1 instance = Demo1.getInstance();

        Demo1 instance1 = Demo1.getInstance();

        //判斷兩個對象是否是同一個
        System.out.println(instance == instance1);
        
    }
}

輸出true 表明是同一個對象,指向同一塊內(nèi)存地址,這樣我們就保證了Demo1單例類只有一個對象被創(chuàng)建

2.餓漢式-方式2 靜態(tài)代碼塊

創(chuàng)建 餓漢式靜態(tài)代碼塊 單例類

public class Demo2 {
    //餓漢式單例類  靜態(tài)代碼塊

    /**
     *私有構(gòu)造方法  讓外界不能創(chuàng)建該類對象
     */
    private Demo2(){}

    /**
     *  聲明一個靜態(tài)的成員變量instance但是不賦值(不創(chuàng)建對象)
     *  沒有為instance賦值,默認(rèn)為null
     */
    private static  Demo2 instance;

    /**
     * 在靜態(tài)代碼快中為instance賦值(創(chuàng)建對象)
     */
    static {
        instance = new Demo2();
    }
    /**
     * 提供一個公共的訪問方式,讓外界可以獲取該類的對象 static是因為外界不需要創(chuàng)建對象,直接通過類訪問
     */
    public static Demo2  getInstance(){
        return instance;
    }
}

創(chuàng)建 餓漢式靜態(tài)代碼塊 測試類

public class Test2 {
    public static void main(String[] args) {
        Demo2 instance = Demo2.getInstance();

        Demo2 instance1 = Demo2.getInstance();

        System.out.println(instance == instance1);
    }
}

輸出true 表明是同一個對象,指向同一塊內(nèi)存地址,這樣我們就保證了Demo2單例類只有一個對象被創(chuàng)建

3.餓漢式-方式3(枚舉方式)

枚舉類實現(xiàn)單例模式是十分推薦的一種單例實現(xiàn)模式,由于枚舉類型是線程安全的,并且只會加載一次,這是十分符合單例模式的特點的,枚舉的寫法很簡單,而且枚舉方式是所有單例實現(xiàn)中唯一一個不會被破環(huán)的單例實現(xiàn)模式

單例類

//枚舉方式創(chuàng)建單例
public enum Singleton {
     INSTANCE;
}

測試類

public class Test1 {
    public static void main(String[] args) {
    Singleton instance = Singleton.INSTANCE;
    Singleton instance1 = Singleton.INSTANCE;


        System.out.println(instance == instance1);
        //輸出 true
        
    }
}

注意:

? 由于枚舉方式是餓漢式,因此根據(jù)餓漢式的特點,枚舉方式也會造成內(nèi)存浪費,但是在不考慮內(nèi)存問題下,枚舉方式是首選,畢竟實現(xiàn)最簡單了

2.2懶漢式

懶漢式:類加載時不會創(chuàng)建該單實例對象,首次使用該對象時才會創(chuàng)建

1.懶漢式-方式1 (線程不安全)

public class Demo3 {
    /**
     *私有構(gòu)造方法  讓外界不能創(chuàng)建該類對象
     */
    private Demo3(){}

    /**
     * 在類中創(chuàng)建該本類對象 static是由于外界獲取該類對象的方法getInstance()是 static
     * 沒有進行賦值(創(chuàng)建對象)
     */
    private static  Demo3 instance;


    /**
     * 提供一個公共的訪問方式,讓外界可以獲取該類的對象 static是因為外界不需要創(chuàng)建對象,直接通過類訪問
     */
    public static Demo3 getInstance(){
        //在首次使用該對象時創(chuàng)建,因此instance賦值也就是對象創(chuàng)建 就是在外界獲取該單例類的方法getInstance()中創(chuàng)建
        instance = new Demo3();
        return instance;
    }

}
public class Test3 {
    public static void main(String[] args) {
        Demo3 instance = Demo3.getInstance();

        Demo3 instance1 = Demo3.getInstance();
        //判斷兩個對象是否是同一個
        System.out.println(instance == instance1);
    }
}

輸出結(jié)果為false,表明我們創(chuàng)建懶漢式單例失敗了。是因為我們在調(diào)用getInstance()時每次調(diào)用都會new一個實例對象,那么也就必然不可能相等了。

   // 如果instance為null,表明還沒有創(chuàng)建該類的對象,那么就進行創(chuàng)建
        if(instance == null){
          instance = new Demo3();
        }
        //如果instance不為null,表明已經(jīng)創(chuàng)建過該類的對象,根據(jù)單例類只能創(chuàng)建一個對象的特點,因此         //我們直接返回instance
        return instance;
    }

注意:

我們在測試是只是單線程,但是在實際應(yīng)用中必須要考慮到多線程的問題。我們假設(shè)一種情況,線程1進入if判斷然后還沒來得及創(chuàng)建instance,這個時候線程1失去了cpu的執(zhí)行權(quán)變?yōu)樽枞麪顟B(tài),線程2獲取cpu執(zhí)行權(quán),然后進行if判斷此時instance還是null,因此線程2為instance賦值創(chuàng)建了該單例對象,那么等到線程1再次獲取cpu執(zhí)行權(quán),也進行了instance賦值創(chuàng)建了該單例對象,單例模式被破壞。

2.懶漢式-方式2 (線程安全)

我們可以通過加synchronized同步鎖的方式保證單例模式在多線程下依舊有效

 public static synchronized Demo3 getInstance(){
        //在首次使用該對象時創(chuàng)建,因此instance賦值也就是對象創(chuàng)建 就是在外界獲取該單例類的方法getInstance()中創(chuàng)建


        // 如果instance為null,表明還沒有創(chuàng)建該類的對象,那么就進行創(chuàng)建

        if(instance == null){
          instance = new Demo3();
        }
        //如果instance不為null,表明已經(jīng)創(chuàng)建過該類的對象,根據(jù)單例類只能創(chuàng)建一個對象的特點,因此我們直接返回instance
        return instance;
    }

注意:

雖然保證了線程安全問題,但是在getInstance()方法上添加了synchronized關(guān)鍵字,導(dǎo)致該方法執(zhí)行效率很低(這是加鎖的一個常見問題)。其實我們可以很容易發(fā)現(xiàn),我們只是在判斷instance時需要解決多線程的安全問題,而沒必要在getInstance()上加鎖

3.懶漢式-方式3(雙重檢查鎖)

對于getInstance()方法來說,絕大部分的操作都是讀操作,讀操作是線程安全的,沒必要讓每個線程必須持有鎖才能調(diào)用該方法,我們可以調(diào)整加鎖的時機。

public class Demo4 {
    /**
     *私有構(gòu)造方法  讓外界不能創(chuàng)建該類對象
     */
    private Demo4(){}

    /**
     *
     * 沒有進行賦值(創(chuàng)建對象) 只是聲明了一個該類的變量
     */
    private static Demo4 instance;


    /**
     * 提供一個公共的訪問方式,讓外界可以獲取該類的對象 static是因為外界不需要創(chuàng)建對象,直接通過類訪問
     */
    public static  Demo4 getInstance(){


        // (第一次判斷)如果instance為null,表明還沒有創(chuàng)建該類的對象,那么就進行創(chuàng)建
        if(instance == null){
            synchronized (Demo4.class){
                //第二次判斷 如果instance不為null
                if(instance == null){
                    instance = new Demo4();
                }
            }

        }

        //如果instance不為null,表明已經(jīng)創(chuàng)建過該單例類的對象,不需要搶占鎖,直接返回
        return instance;
    }

}

雙重檢查鎖模式完美的解決了單例、性能、線程安全問題,但是只是這樣還是有問題的…

JVM在創(chuàng)建對象時會進行優(yōu)化和指令重排,在多線程下可能會發(fā)生空指針異常的問題,可以使用volatile關(guān)鍵字,volatile可以保證可見性和有序性。

 private static volatile Demo4  instance;

image-20220322203534148

如果發(fā)生指令重排 2 和 3 的步驟顛倒,那么instance會指向一塊虛無的內(nèi)存(也有可能是有數(shù)據(jù)的一塊內(nèi)存)

完整代碼

public class Demo4 {
    /**
     *私有構(gòu)造方法  讓外界不能創(chuàng)建該類對象
     */
    private Demo4(){}

    /**
     * volatile可以保證有序性
     * 沒有進行賦值(創(chuàng)建對象) 只是聲明了一個該類的變量
     */
    private static volatile Demo4  instance;
    
    /**
     * 提供一個公共的訪問方式,讓外界可以獲取該類的對象 static是因為外界不需要創(chuàng)建對象,直接通過類訪問
     */
    public static  Demo4 getInstance(){
        // (第一次判斷)如果instance為null,表明還沒有創(chuàng)建該類的對象,那么就進行創(chuàng)建
        if(instance == null){
            synchronized (Demo4.class){
                //第二次判斷 如果instance不為null
                if(instance == null){
                    instance = new Demo4();
                }
            }
        }

        //如果instance不為null,表明已經(jīng)創(chuàng)建過該單例類的對象,不需要搶占鎖,直接返回
        return instance;
    }
}

4.懶漢式-4 (靜態(tài)內(nèi)部類)

靜態(tài)內(nèi)部類單例模式中實例由內(nèi)部類創(chuàng)建,由于JVM在加載外部類的過程中,是不會加載靜態(tài)內(nèi)部類的,只有內(nèi)部類的屬性/方法被調(diào)用時才會被加載,并初始化其靜態(tài)屬性。靜態(tài)屬性由于被final修飾,保證只被實例化一次,并且嚴(yán)格保證實例化順序。

創(chuàng)建單例類

public class Singleton {

    private Singleton(){}

    /**
     *定義一個靜態(tài)內(nèi)部類
     */
    private static  class SingletonHolder{
        //在靜態(tài)內(nèi)部類中創(chuàng)建外部類的對象
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

創(chuàng)建測試類

public class Test4 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();

        Singleton instance1 = Singleton.getInstance();
        //判斷兩個對象是否是同一個
        System.out.println(instance == instance1);
    }
}

注意:

? 第一次加載Singleton類時不會去初始化INSTANCE,只有在調(diào)用getInstance()方法時,JVM加載SingletonHolder并初始化INSTANCE,這樣可以保證線程安全,并且Singleton類的唯一性

? 靜態(tài)內(nèi)部類單例模式是一種開源項目比較常用的單例模式,在沒有任何加鎖的情況下保證多線程的安全,并且沒有任何性能和空間上的浪費

3.單例模式的破壞

單例模式最重要的一個特點就是只能創(chuàng)建一個實例對象,那么如果能使單例類能創(chuàng)建多個就破壞了單例模式(除了枚舉方式)破壞單例模式的方式有兩種:

3.1序列化和反序列化

從以上創(chuàng)建單例模式的方式中任選一種(除枚舉方式),例如靜態(tài)內(nèi)部類方式

//記得要實現(xiàn)Serializable序列化接口
public class Singleton implements Serializable {

    private Singleton(){}

    /**
     *定義一個靜態(tài)內(nèi)部類
     */
    private static  class SingletonHolder{
        //在靜態(tài)內(nèi)部類中創(chuàng)建外部類的對象
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

測試類

public class Test1 {

    public static void main(String[] args) throws IOException {
              writeObjectToFile();
    }


    /**
     * 向文件中寫數(shù)據(jù)(對象)
     * @throws IOException
     */
    public static void writeObjectToFile() throws IOException {
        //1.獲取singleton對象
        Singleton instance = Singleton.getInstance();
        //2.創(chuàng)建對象輸出流對象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\1.txt"));
        //3.寫對象
        oos.writeObject(instance);
        //4.釋放資源
        oos.close();


    }
}

在d盤根目錄下出現(xiàn)一個文件1.txt由于數(shù)據(jù)是序列化后的 咱也看不懂

然后我們從這個文件中讀取instance對象

public static void main(String[] args) throws Exception {
             // writeObjectToFile();
        readObjectFromFile();
        readObjectFromFile();
    }
    /**
     * 從文件中讀數(shù)據(jù)(對象)
     * @throws Exception
     */
    public static  void readObjectFromFile() throws Exception {

        //1.創(chuàng)建對象輸入流對象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\1.txt"));
        //2.讀對象
        Singleton instance = (Singleton) ois.readObject();
        System.out.println(instance);
        //3.釋放資源
        ois.close();
    }

輸出結(jié)果不相同,結(jié)論為:序列化破壞了單例模式,兩次讀的對象不一樣了

com.xue.demo01.Singleton@2328c243
com.xue.demo01.Singleton@bebdb06

解決方案

在singleton中添加readResolve方法

  /**
     * 當(dāng)進行反序列化時,會自動調(diào)用該方法,將該方法的返回值直接返回
     * @return
     */
    public Object readResolve(){
        return SingletonHolder.INSTANCE;
    }

重新進行寫和讀,發(fā)現(xiàn)兩次讀的結(jié)果是相同的,解決了序列化破壞單例模式的問題

為什么在singleton單例類中添加readResolve方法就可以解決序列化破壞單例的問題呢,我們在ObjectInputStream源碼中在readOrdinaryObject方法中

 private Object readOrdinaryObject(boolean unshared)
        throws IOException{
//代碼段   
Object obj;
        try {
            //isInstantiable如果一個實現(xiàn)序列化的類在運行時被實例化就返回true
            //desc.newInstance()會通過反射調(diào)用無參構(gòu)造創(chuàng)建一個新的對象
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

   //代碼段

   if (obj != null &&
            handles.lookupException(passHandle) == null &&
       //hasReadResolveMethod 如果實現(xiàn)序列化接口的類中定義了readResolve方法就返回true
            desc.hasReadResolveMethod())
        {
       //通過反射的方式調(diào)用被反序列化類的readResolve方法
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
       
    //代碼段
 }

3.2反射

從以上創(chuàng)建單例模式的方式中任選一種(除枚舉方式),例如靜態(tài)內(nèi)部類方式

測試類

public class Test1 {

    public static void main(String[] args) throws Exception {


        //1.獲取Singleton的字節(jié)碼對象
        Class<Singleton> singletonClass = Singleton.class;

        //2.獲取無參構(gòu)造方法對象
        Constructor cons = singletonClass.getDeclaredConstructor();

        //3.取消訪問檢查
        cons.setAccessible(true);
        //4.反射創(chuàng)建對象
        Singleton instance1 = (Singleton) cons.newInstance();

        Singleton instance2 = (Singleton) cons.newInstance();

        System.out.println(instance1 == instance2);
        //輸出false 說明反射破壞了單例模式
    }


}

解決方案:

public class Singleton  {

    //static是為了都能訪問
    private static boolean flag = false;

    private Singleton() {
        //加上同步鎖,防止多線程并發(fā)問題
        synchronized (Singleton.class) {
            //判斷flag是否為true,如果為true說明不是第一次創(chuàng)建,拋異常
            if (flag) {
                throw new RuntimeException("不能創(chuàng)建多個對象");
            }
            //flag的值置為true
            flag = true;
        }
    }

    /**
     *定義一個靜態(tài)內(nèi)部類
     */
    private static  class SingletonHolder{
        //在靜態(tài)內(nèi)部類中創(chuàng)建外部類的對象
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

這樣就不能通過之前的反射方式破壞單例模式了,但是如果通過反射修改flag的值也是可以破壞單例模式的,但是這樣可以防止意外反射破壞單例模式,如果刻意破壞是很難防范的,畢竟反射太強了??????

到此這篇關(guān)于Java超詳細(xì)講解設(shè)計模式之一的單例模式的文章就介紹到這了,更多相關(guān)Java 單例模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java數(shù)據(jù)結(jié)構(gòu)實現(xiàn)順序表示例

    java數(shù)據(jù)結(jié)構(gòu)實現(xiàn)順序表示例

    這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)實現(xiàn)順序表示例,需要的朋友可以參考下
    2014-03-03
  • 解決springboot讀取application.properties中文亂碼問題

    解決springboot讀取application.properties中文亂碼問題

    初用properties,讀取java properties文件的時候如果value是中文,會出現(xiàn)亂碼的問題,所以本文小編將給大家介紹如何解決springboot讀取application.properties中文亂碼問題,需要的朋友可以參考下
    2023-11-11
  • Java 泛型實例詳解

    Java 泛型實例詳解

    本文主要介紹Java 泛型的知識,這里給代碼實例對Java 泛型深度理解,有需要的朋友可以看下
    2016-07-07
  • java多線程編程之Synchronized關(guān)鍵字詳解

    java多線程編程之Synchronized關(guān)鍵字詳解

    這篇文章主要為大家詳細(xì)介紹了java多線程編程之Synchronized關(guān)鍵字,感興趣的朋友可以參考一下
    2016-05-05
  • Mybatis返回插入主鍵id的方法

    Mybatis返回插入主鍵id的方法

    這篇文章主要介紹了 Mybatis返回插入主鍵id的方法,在文章底部給大家補充了Mybatis中insert中返回主鍵ID的方法,非常不錯,需要的朋友可以參考下
    2017-04-04
  • mybatisplus?@Select注解中拼寫動態(tài)sql異常問題的解決

    mybatisplus?@Select注解中拼寫動態(tài)sql異常問題的解決

    這篇文章主要介紹了mybatisplus?@Select注解中拼寫動態(tài)sql異常問題的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 用Java打印九九除法表代碼分析

    用Java打印九九除法表代碼分析

    這篇文章主要介紹了如何用Java語言打印九九除法表,包括其使用的源代碼,需要的朋友可以參考下。
    2017-08-08
  • Java中反射的學(xué)習(xí)筆記分享

    Java中反射的學(xué)習(xí)筆記分享

    反射是Java編程語言中的一個特性。它允許執(zhí)行的Java程序檢查或?操作?自身,并操作程序的內(nèi)部屬性。本文將通過幾個示例帶大家詳細(xì)了解一下Java中反射的使用,需要的可以參考一下
    2022-11-11
  • dom4j操作xml的demo(分享)

    dom4j操作xml的demo(分享)

    下面小編就為大家?guī)硪黄猟om4j操作xml的demo(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • 一文搞懂Java中的抽象類和接口到底是什么

    一文搞懂Java中的抽象類和接口到底是什么

    在類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類稱為抽象類,接口是Java中最重要的概念之一,它可以被理解為一種特殊的類,不同的是接口的成員沒有執(zhí)行體,是由全局常量和公共的抽象方法所組成,本文給大家介紹Java抽象類和接口,感興趣的朋友一起看看吧
    2022-04-04

最新評論