溫馨提示×

溫馨提示×

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

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

怎么實現(xiàn)Java單例模式

發(fā)布時間:2021-11-17 09:31:07 來源:億速云 閱讀:188 作者:iii 欄目:大數(shù)據(jù)

本篇內(nèi)容主要講解“怎么實現(xiàn)Java單例模式”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么實現(xiàn)Java單例模式”吧!

介紹

單例模式(Singleton Pattern)是一個比較簡單的設計模式,屬于創(chuàng)建型模式。其定義為

確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例

在系統(tǒng)中,單例模式要求一個單例對象只能有一個實例,這類對象如果有多個實例就可能會產(chǎn)生一些問題,如:資源消耗過多,處理結(jié)果不一致等,一般單例會有以下使用場景

  • 生成唯一 序列號

  • 整個項目的共享訪問點或共享數(shù)據(jù),如Web頁面計數(shù)器

  • 創(chuàng)建一個對象實例需要消耗過多資源,如I/O和數(shù)據(jù)庫連接等

在Java中,一個單例對象在一個JVM中,只會有一個實例存在。以下是單例模式結(jié)構(gòu)圖

怎么實現(xiàn)Java單例模式

  • 有一個該單例對象的靜態(tài)成員變量

  • 私有的構(gòu)造函數(shù),只能被自身實例化

  • 提供一個靜態(tài)公共方法實例化對象,并訪問該對象實例

單例模式實現(xiàn)

單例模式有兩種實現(xiàn)方式:

  • 餓漢式

  • 懶漢式(延遲加載)

餓漢式

/**
 * 餓漢式單例
 */
public class Singleton1 {
    // 靜態(tài)成員變量,在靜態(tài)初始化時便實例化對象
    private static final Singleton1 singleton = new Singleton1();
    // 構(gòu)造私有
    private Singleton1(){
    }
    public static Singleton1 getSingletonInstance(){
        return singleton;
    }
}

餓漢式指的是,在類加載的時候便實例化了該單例對象,不管有沒有使用,先創(chuàng)建了再說。這種方式是可以保證線程安全的,但是如果該對象一直沒有被使用,就浪費了空間資源。

但是對于一些空間占用較大、或是只在某些特定場景才使用的單例,我們會想要在第一次使用的時候才去實例化,這時,就需要懶漢式的延遲加載

懶漢式

/**
 * 懶漢式單例(非線程安全)
 */
public class Singleton2 {
    private static Singleton2 singleton;
    private Singleton2(){}
    // 獲取實例
    public static Singleton2 getSingletonInstance(){
        if(singleton == null){
            singleton =  new Singleton2();
        }
        return singleton;
    }
}

從上面代碼可以看出,懶漢式餓漢式在于單例對象的創(chuàng)建時機。餓漢式是在類加載時便實例化對象,調(diào)用時無須判斷直接返回即可;而懶漢式是在第一次調(diào)用時實例化,并且每次調(diào)用都需要判斷是否已經(jīng)實例化

但是上面的這種方式在多線程下是不安全的,多個線程同時訪問getSingletonInstance()時,可能會創(chuàng)建多個實例,便不再是單例了。那怎么解決線程安全的問題呢?首先我們可能會想到對getSingletonInstance()方法加上synchronized關鍵字

/**
 * 懶漢式單例(synchronized關鍵字線程安全)
 */
public class Singleton3 {
    private static Singleton3 singleton;
    private Singleton3(){}
    // 獲取實例
    public static synchronized Singleton3 getSingletonInstance(){
        if(singleton == null){
            singleton =  new Singleton3();
        }
        return singleton;
    }
}

getSingletonInstance()方法加上了同步鎖,增加了獲取實例的時間消耗,且在多線程下可能會發(fā)生阻塞。但其實我們并不想每次獲取實例的時候都去加上鎖,只是想在第一次調(diào)用創(chuàng)建對象時保證線程安全即可

雙重校驗鎖(DCL)

getSingletonInstance()方法加上鎖,確實能保證線程安全,卻存在性能的問題。是不是要必要對整個方法加鎖?還是當我檢查到實例還沒有創(chuàng)建,才去同步

**雙重校驗鎖(double-checked locking,DCL)**是能解決這個問題的

/**
 * 雙重校驗鎖(double-checked locking,DCL)
 */
public class Singleton4 {
     /**
     * 成員變量這里會加上關鍵字 volatile,目的是為了防止指令重排序
     */
    private static volatile Singleton4 singleton;
    private Singleton4(){}
    // 獲取實例
    public static Singleton4 getSingletonInstance(){
        // 第一次校驗,沒有實例化才進入同步代碼塊
        if(singleton == null){
            synchronized (Singleton4.class){
                // 進入同步代碼塊后,再判斷,如果為空才創(chuàng)建實例
                if(singleton == null){
                    singleton =  new Singleton4();
                }
            }
        }
        return singleton;
    }
}

不對方法加上鎖,只對創(chuàng)建實例的代碼加鎖即可。方法中會有兩次判空的操作,第一次是為了不必要的同步,為null才進入同步代碼塊,第二次是進入同步代碼塊后判斷為null才創(chuàng)建實例

注意:這里的成員變量加上了volatile關鍵字

使用volatile可以保證數(shù)據(jù)的可見性,不過synchronized也是能保證同步數(shù)據(jù)的可見性的,這里使用volatile更多的目的是為了禁止Java指令重排序

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

/**
 * 靜態(tài)內(nèi)部類
 */
public class Singleton5 {
    private Singleton5(){}

    // 獲取實例
    public static Singleton5 getSingletonInstance(){
        return SingletonHolder.SINGLETON;
    }

    /**
     * 內(nèi)部類,JVM在類加載的時候,是互斥的,可以保證線程安全
     */
    private static class SingletonHolder{
        private static final Singleton5 SINGLETON = new Singleton5();
    }
}

JVM在類加載的時候是會保證數(shù)據(jù)同步的,我們可以通過內(nèi)部類來創(chuàng)建單例對象。第一次加載Singleton5時并不會加載內(nèi)部類,不去使用內(nèi)部類的時候,該內(nèi)部類就不會加載。只有第一次調(diào)用getSingletonInstance()方法,會去加載內(nèi)部類并實例化單例對象,這樣就可以做到延遲加載和線程安全了

枚舉方式

使用枚舉方式來實現(xiàn)單例是非常簡潔的,支持序列化機制,絕對防止多次實例化

/**
 * 枚舉方式
 */
public enum Singleton6 {

    /**
     * 枚舉方式實現(xiàn)單例
     */
    SINGLETON;

    public void handle() {
        // to do something
    }
}

該單例的使用方法

public class SingletonDemo {

    @Test
    public void test(){
        // 枚舉方式
        Singleton6 singleton = Singleton6.SINGLETON;
        singleton.handle();
    }
}

到此,相信大家對“怎么實現(xiàn)Java單例模式”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!

向AI問一下細節(jié)

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

AI