徹底弄懂 單例設計模式


一、設計模式簡介

設計模式是在大量的實踐中總結和理論化之后優選的代碼結構、編程風格、以及解決問題的思考方式。設計模式免去我們自己再思考和摸索。就像是經典的棋譜,不同的棋局,我們用不同的棋譜。”套路”

二、單例設計模式

2.1 單列設計模式簡介

所謂類的單例設計模式,就是采取一定的方法保證在整個的軟件系統中,對某個類只能存在一個對象實例,並且該類只提供一個取得其對象實例的方法。

2.2 單例設計模式優點

由於單例模式只生成一個實例,減少了系統性能開銷,當一個對象的產生需要比較多的資源時,如讀取配置、產生其他依賴對象時,則可以通過在應用啟動時直接產生一個單例對象,然后永久駐留內存的方式來解決。

2.3 單例設計模式思路

  1. 如果我們要讓類在一個虛擬機中只能產生一個對象,我們首先必須將類的構造器的訪問權限設置為private【private權限修飾符,可以修飾成員變量、成員方法、內部類。作用:只能在本類中使用privite修飾的成員變量、成員方法】,這樣,就不能用new操作符在類的外部產生類的對象了,但在類內部仍可以產生該類的對象。

  2. 因為在類的外部開始還無法得到類的對象,只能調用該類的某個靜態方法以返回類內部創建的對象,靜態方法只能訪問類中的靜態成員變量,所以,指向類內部產生的該類對象的變量也必須定義成靜態的。

2.4 單例(Singleton)設計模式-餓漢式

直接上代碼:

/**單例(Singleton)設計模式-餓漢式
 * private權限修飾符,可以修飾成員變量、成員方法、內部類。作用:只能在本類中使用private修飾的成員變量、成員方法】
 */
public class Student {
    
    // 第一步:私有化構造器。【讓外部無法通過new來創建對象】
    private Student() {
    }

    
    // 第二步:創建一個對象
    /*  1. 為什么怎么做?
           不怎么做的話,我們就沒有對象呀!構造器已經被我們設置為私有的了,外部不能造對象了,那我們就只能在內部提供一個對象。
        2. 為什么設置為私有的?
            想一想,如果不設置為私有的。當有一個類繼承這個類的時候,該子類就可以訪問這個屬性了,從而得到了父類對象。
            這與我們初衷相違背。
        3.為什么設置為靜態的?
            看第三步中的回答,從第三步反推過來的。需要把這個屬性設置為私有的。
    * */
    private static Student student = new Student();

    
    // 第三步:對外提供一個公共靜態方法。【以便獲得第二步中創建的對象】
    /*1. 為什么是公共的?
    *    肯定是公共的,不然如私有方法,外部怎么能夠訪問得了,從而怎么得到這個唯一的對象
    * 2. 為什么是靜態方法?
    *     第一:如果不是靜態方法,我們需要通過new對象來訪問該方法,和我們初衷相違背。
    *     第二:靜態方法,我們可以通過類名.方法名的方式進行訪問。
    *     第三:靜態方法不能被重寫。
    *     第四:靜態方法里面只能訪問靜態屬性、靜態方法,從而反推出第二步驟中的屬性需要加static關鍵字
    * */

    public static Student getStudent() {
        return student;
    }
}

測試:

public class TestStudent {
    public static void main(String[] args) {
       // 1. 我們先來new一下,發現編譯失敗。【說明:外部通過構造器無法創建對象】
       // Student student = new Student();

        // 2. 比較創出的對像否是同一個對象,結果為true。【說明:證明是同一個對象】
        Student student1 = Student.getStudent();
        Student student2 = Student.getStudent();
        System.out.println(student1==student2);
    }
}

2.5 單例(Singleton)設計模式-懶漢式

錯誤寫法:第三步有問題

/**
 * 單例(Singleton)設計模式-懶漢式
 */
public class Teacher {
    // 第一步:私有化構造器
    private Teacher() {

    }
    // 第二步:聲明當前類對象,沒有初始化
    private static Teacher teacher=null;

    // 第三步:提供公共靜態方法,返回當前類的對象
    public static Teacher getTeacher() {
        Teacher teacher = new Teacher();
        return teacher;
    }
}

測試:這顯然是兩個對象

錯誤原因:調用一次方法,便會new一次,創建不同的對象。

正確寫法:

/**
 * 單例(Singleton)設計模式-懶漢式
 */
public class Teacher {
    // 第一步:私有化構造器
    private Teacher() {

    }
    // 第二步:聲明當前類對象,沒有初始化
    private static Teacher teacher=null;

    // 第三步:提供公共靜態方法,返回當前類的對象
    public static Teacher getTeacher() {
        if (teacher == null) {    // 先判斷一下,如果該對象沒有創建就new,return出去。如果已經有了就不創建了。
            Teacher teacher = new Teacher();
        }
        return teacher;
    }
}

2.6 餓漢式VS懶漢式

  1. 餓漢式

    ​ 壞處:對象加載時間過長【占用內存】

    ​ 好處:餓漢式是線程安全的

  2. 懶漢式

    ​ 好處:延遲對象的創建

    ​ 壞處:目前的寫法,存在線程不安全。

    【線程不安全解釋:假設有兩個線程進入了getTeacher方法,線程一判斷teacher會null時,此時CPU資源給到了線程二(線程一沒有執行Teacher teacher = new Teacher()😉,線程二也判斷teacher為null,執行Teacher teacher = new Teacher();語句。從而線程一、線程二得到的對象不是同一個對象】

2.7 同步機制解決懶漢式線程安全問題

方式一:同步方法

/**
 * 單例(Singleton)設計模式-懶漢式
 */
public class Teacher {
    // 第一步:私有化構造器
    private Teacher() {

    }
    // 第二步:聲明當前類對象,沒有初始化
    private static Teacher teacher=null;

    // 第三步:提供公共靜態方法,返回當前類的對象
    public static synchronized Teacher getTeacher() { // 同步監視器(鎖),該靜態方法中為:Teacher.class
        if (teacher == null) {
            Teacher teacher = new Teacher();
        }
        return teacher;
    }
}

方式二:同步代碼塊

/**
 * 單例(Singleton)設計模式-懶漢式
 */
public class Teacher {
    // 第一步:私有化構造器
    private Teacher() {

    }
    // 第二步:聲明當前類對象,沒有初始化
    private static Teacher teacher=null;

    // 第三步:提供公共靜態方法,返回當前類的對象
    public static Teacher getTeacher() {
        /*外面在加一層判斷,效率較高。
        * 剛開始時比如有3個線程進來,此項teacher為null,都進到里面,3個線程排隊。
        * 但是后面來的線程,就不用進同步代碼塊了【大家不用在同步代碼塊外面等待,而是判斷teacher為true,直接拿走teacher對象即可】
        * */
        if (teacher == null) {	
            synchronized (Teacher.class) {
                if (teacher == null) {
                    Teacher teacher = new Teacher();
                }
            }
        }
        return teacher;
    }
}

2.8 總結

相對來說把線程安全解決后的懶漢式單例模式是優於餓漢式單例模式的。本篇知識點涉及到java的面向對象知識、關鍵字、修飾符、多線程等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM