国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

單例模式總結

xorpay / 1897人閱讀

摘要:如果是后者,則在執行完畢未執行之前,被線程二搶占了,這時已經是非了但卻沒有初始化,所以線程二會直接返回在之后雙重檢查鎖定才能夠正常達到單例效果,之前有個坑。所以,在版本前,雙重檢查鎖形式的單例模式是無法保證線程安全的。

第一種(懶漢, 線程不安全):

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

這種寫法 lazy loading 很明顯, 但是致命的是在多線程不能正常工作。

第二種(懶漢, 線程安全):

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

這種寫法能夠在多線程中很好的工作, 而且看起來它也具備很好的 lazy loading, 但是, 遺憾的是, 效率很低, 99% 情況下不需要同步。

第三種(餓漢):

public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

這種方式基于 classloder 機制避免了多線程的同步問題, 不過, instance 在類裝載時就實例化, 雖然導致類裝載的原因有很多種, 在單例模式中大多數都是調用 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載, 這時候初始化 instance 顯然沒有達到 lazy loading 的效果。

第四種(餓漢, 變種):

public class Singleton {
    private Singleton instance = null;

    static {
        instance = new Singleton();
    }

    private Singleton() {
    }

    public static Singleton getInstance() {
        return this.instance;
    }
}

表面上看起來差別挺大, 其實更第三種方式差不多, 都是在類初始化即實例化 instance。

第五種(靜態內部類):

public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {
    }

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

這種方式同樣利用了classloder的機制來保證初始化instance時只有一個線程, 它跟第三種和第四種方式不同的是(很細微的差別):第三種和第四種方式是只要Singleton類被裝載了, 那么instance就會被實例化(沒有達到lazy loading效果), 而這種方式是Singleton類被裝載了, instance不一定被初始化。因為SingletonHolder類沒有被主動使用, 只有顯示通過調用getInstance方法時, 才會顯示裝載SingletonHolder類, 從而實例化instance。想象一下, 如果實例化instance很消耗資源, 我想讓他延遲加載, 另外一方面, 我不希望在Singleton類加載時就實例化, 因為我不能確保Singleton類還可能在其他的地方被主動使用從而被加載, 那么這個時候實例化instance顯然是不合適的。這個時候, 這種方式相比第三和第四種方式就顯得很合理。

第六種(枚舉):

public enum Singleton {
    INSTANCE;

    public void whateverMethod() {
    }
}

這種方式是 Effective Java作者 Josh Bloch 提倡的方式, 它不僅能避免多線程同步問題, 而且還能防止反序列化重新創建新的對象, 可謂是很堅強的壁壘啊, 不過, 個人認為由于 1.5 中才加入 enum 特性, 用這種方式寫不免讓人感覺生疏, 在實際工作中, 我也很少看見有人這么寫過。

第七種(雙重校驗鎖):

public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

這個是第二種方式的升級版, 俗稱雙重檢查鎖定, 也有瑕疵。
主要在于singleton = new Singleton()這句,這并非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情。

給 singleton 分配內存

調用 Singleton 的構造函數來初始化成員變量,形成實例

將singleton對象指向分配的內存空間(執行完這步 singleton才是非 null 了)
但是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執行完畢、2 未執行之前,被線程二搶占了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance.

在JDK1.5之后, 雙重檢查鎖定才能夠正常達到單例效果,1.5之前有個坑。

說這個坑之前我們要先來看看volatile這個關鍵字。其實這個關鍵字有兩層語義。第一層語義相信大家都比較熟悉,就是可見性。可見性指的是在一個線程中對該變量的修改會馬上由工作內存(Work Memory)寫回主內存(Main Memory),所以會馬上反應在其它線程的讀取操作中。順便一提,工作內存和主內存可以近似理解為實際電腦中的高速緩存和主存,工作內存是線程獨享的,主存是線程共享的。volatile的第二層語義是禁止指令重排序優化。大家知道我們寫的代碼(尤其是多線程代碼),由于編譯器優化,在實際執行的時候可能與我們編寫的順序不同。編譯器只保證程序執行結果與源代碼相同,卻不保證實際指令的順序與源代碼相同。這在單線程看起來沒什么問題,然而一旦引入多線程,這種亂序就可能導致嚴重問題。volatile關鍵字就可以從語義上解決這個問題。
但是很不幸,禁止指令重排優化這條語義直到jdk1.5以后才能正確工作。此前的JDK中即使將變量聲明為volatile也無法完全避免重排序所導致的問題。所以,在jdk1.5版本前,雙重檢查鎖形式的單例模式是無法保證線程安全的。

總結

有兩個問題需要注意:

如果單例由不同的類裝載器裝入, 那便有可能存在多個單例類的實例。假定不是遠端存取, 例如一些servlet容器對每個servlet使用完全不同的類 裝載器, 這樣的話如果有兩個servlet訪問一個單例類, 它們就都會有各自的實例。

如果 Singleton 實現了 java.io.Serializable 接口, 那么這個類的實例就可能被序列化和復原。不管怎樣, 如果你序列化一個單例類的對象, 接下來復原多個那個對象, 那你就會有多個單例類的實例。

對第一個問題修復的辦法:

private static Class getClass(String classname) throws ClassNotFoundException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        if (classLoader == null) {
            classLoader = Singleton.class.getClassLoader();
        }

        return (classLoader.loadClass(classname));
}

對第二個問題修復的辦法:

public class Singleton implements java.io.Serializable {
    public static Singleton INSTANCE = new Singleton();

    protected Singleton() {
    }

    private Object readResolve() {
        return INSTANCE;
    }
}

對我來說, 我比較喜歡第三種和第五種方式, 簡單易懂, 而且在JVM層實現了線程安全(如果不是多個類加載器環境), 一般的情況下, 我會使用第三種方式, 只有在要明確實現lazy loading效果時才會使用第五種方式, 另外, 如果涉及到反序列化創建對象時我會試著使用枚舉的方式來實現單例, 不過, 我一直會保證我的程序是線程安全的, 而且我永遠不會使用第一種和第二種方式, 如果有其他特殊的需求, 我可能會使用第七種方式, 畢竟, JDK1.5已經沒有雙重檢查鎖定的問題了。
不過一般來說, 第一種不算單例, 第四種和第三種就是一種, 如果算的話, 第五種也可以分開寫了。所以說, 一般單例都是五種寫法。懶漢, 惡漢, 雙重校驗鎖, 枚舉和靜態內部類。

三大要點

線程安全

延遲加載

序列化與反序列化安全

除了枚舉形式, 其他實現方式都有兩個共同的缺點

都需要額外的工作(Serializable、transient、readResolve())來實現序列化,否則每次反序列化一個序列化的對象實例時都會創建一個新的實例。

可能會有人使用反射強行調用我們的私有構造器(如果要避免這種情況,可以修改構造器,讓它在創建第二個實例的時候拋異常)。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66861.html

相關文章

  • 慕課網_《模式的秘密之單例模式》學習總結

    時間:2017年08月27日星期日說明:本文部分內容均來自慕課網。@慕課網:http://www.imooc.com教學源碼:https://github.com/zccodere/s...學習源碼:https://github.com/zccodere/s... 第一章:單例模式簡介 1-1 簡介 單例模式 概念及應用場合 餓漢模式 懶漢模式 餓漢模式與懶漢模式的區別 什么是設計模式 是一套被反...

    afishhhhh 評論0 收藏0
  • Java 設計模式單例模式

    摘要:在設計模式一書中,將單例模式稱作單件模式。通過關鍵字,來保證不會同時有兩個線程進入該方法的實例對象改善多線程問題為了符合大多數程序,很明顯地,我們需要確保單例模式能在多線程的情況下正常工作。 在《Head First 設計模式》一書中,將單例模式稱作單件模式。這里為了適應大環境,把它稱之為大家更熟悉的單例模式。 一、了解單例模式 1.1 什么是單例模式 單例模式確保一個類只有一個實例,...

    everfight 評論0 收藏0
  • Android中的設計模式單例模式

    摘要:總結單例是運用頻率很高的模式,因為客戶端沒有高并發的情況,選擇哪種方式并不會有太大的影響,出于效率考慮,推薦使用和靜態內部類實現單例模式。 單例模式介紹 單例模式是應用最廣的模式之一,也可能是很多人唯一會使用的設計模式。在應用單例模式時,單例對象的類必須保證只用一個實例存在。許多時候整個系統只需要一個全局對象,這樣有利于我么能協調整個系統整體的行為。 單例模式的使用場景 確保某個類有且...

    yzd 評論0 收藏0
  • 再遇設計模式之JavaScript篇

    摘要:在面向對象的語言中,比如,等,單例模式通常是定義類時將構造函數設為,保證對象不能在外部被出來,同時給類定義一個靜態的方法,用來獲取或者創建這個唯一的實例。 萬事開頭難,作為正經歷菜鳥賽季的前端player,已經忘記第一次告訴自己要寫一些東西出來是多久以的事情了。。。如果,你也和我一樣,那就像我一樣,從現在開始,從看到這篇文章開始,打開電腦,敲下你的第一篇文章(或者任何形式的文字)吧。 ...

    Clect 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<