設(shè)計模式 - 單例模式(詳解)看看和你理解的是否一樣瘸彤?

一、概述

單例模式是設(shè)計模式中相對簡單且非常常見的一種設(shè)計模式笛钝,但是同時也是非常經(jīng)典的高頻面試題质况,相信還是有很多人在面試時會掛在這里。本篇文章主要針對單例模式做一個回顧婆翔,記錄單例模式的應(yīng)用場景拯杠、常見寫法、針對線程安全進行調(diào)試(看得見的線程)以及總結(jié)啃奴。相信大家看完這篇文章之后潭陪,對單例模式有一個非常深刻的認識。

文章中按照常見的單例模式的寫法最蕾,由淺入深進行講解記錄依溯;以及指出該寫法的不足,從而進行演進改造瘟则。

秉承廢話少說的原則黎炉,我們下面快速開始

二、定義

單例模式(Singleton Pattern)是指確保一個類在任何情況下都絕對只有一個實例醋拧,并提供一個全局訪問點慷嗜。

單例模式是創(chuàng)建型模式。

三丹壕、應(yīng)用場景

  1. 生活中的單例:例如庆械,國家主席、公司 CEO菌赖、部門經(jīng)理等缭乘。
  2. Java世界中:ServletContextServletContextConfig 等;
  3. Spring 框架應(yīng)用中:ApplicationContext琉用、數(shù)據(jù)庫的連接池也都是單例形式堕绩。

四策幼、常見的單例模式寫法

單例模式主要有:餓漢式單例、懶漢式單例(線程不安全型奴紧、線程安全型特姐、雙重檢查鎖類型、靜態(tài)內(nèi)部類類型)绰寞、注冊式(登記式)單例(枚舉式單例到逊、容器式單例)、ThreadLocal線程單例

下面我們來看看各種模式的寫法滤钱。

1、餓漢式單例

餓漢式單例是在類加載的時候就立即初始化脑题,并且創(chuàng)建單例對象件缸。絕對線程安全,在線程還沒出現(xiàn)以前就是實例化了叔遂,不可能存在訪問安全問題他炊。

Spring 中 IOC 容器 ApplicationContext 就是典型的餓漢式單例

優(yōu)缺點

優(yōu)點:沒有加任何的鎖、執(zhí)行效率比較高已艰,在用戶體驗上來說痊末,比懶漢式更好。

缺點:類加載的時候就初始化哩掺,不管用與不用都占著空間凿叠,浪費了內(nèi)存,有可能占著茅坑不拉屎嚼吞。

寫法

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午9:26
 */
public class HungrySingleton {
    // 1.私有化構(gòu)造器
    private HungrySingleton (){}
    // 2.在類的內(nèi)部創(chuàng)建自行實例
    private static final HungrySingleton instance = new HungrySingleton();
    // 3.提供獲取唯一實例的方法(全局訪問點)
    public static HungrySingleton getInstance(){
        return instance;
    }
}

還有另外一種寫法盒件,利用靜態(tài)代碼塊的機制:

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午10:46
 */
public class HungryStaticSingleton {
    // 1. 私有化構(gòu)造器
    private HungryStaticSingleton(){}

    // 2. 實例變量
    private static final HungryStaticSingleton instance;

    // 3. 在靜態(tài)代碼塊中實例化
    static {
        instance = new HungryStaticSingleton();
    }

    // 4. 提供獲取實例方法
    public static HungryStaticSingleton getInstance(){
        return instance;
    }
}

測試代碼,我們創(chuàng)建 10 個線程(具體線程發(fā)令槍 ConcurrentExecutor 在文末源碼中獲取):

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午11:17
 */
public class HungrySingletonTest {
    @Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                HungrySingleton instance = HungrySingleton.getInstance();
                System.out.println(Thread.currentThread().getName() + " : " + instance);
            }, 10, 10);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

測試結(jié)果:

pool-1-thread-6 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-9 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-10 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-7 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-8 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
...

可以看到舱禽,餓漢式每次獲取實例都是同一個炒刁。

使用場景

這兩種寫法都非常的簡單,也非常好理解誊稚,餓漢式適用在單例對象較少的情況翔始。

下面我們來看性能更優(yōu)的寫法——懶漢式單例。


2里伯、懶漢式單例

懶漢式單例的特點是:被外部類調(diào)用的時候內(nèi)部類才會加載城瞎。

懶漢式單例可以分為下面這幾種寫法來。

簡單懶漢式(線程不安全)

這是懶漢式單例的簡單寫法

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午10:55
 */
public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    private static LazySimpleSingleton instance = null;

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

我們創(chuàng)建一個多線程來測試一下俏脊,是否線程安全:

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午11:12
 */
public class LazySimpleSingletonTest {

    @Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
                System.out.println(Thread.currentThread().getName() + " : " + instance);
            }, 5, 5);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

運行結(jié)果:

pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@748e48d0
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f

從測試結(jié)果來看全谤,一定幾率出現(xiàn)創(chuàng)建兩個不同結(jié)果的情況,意味著上面的單例存在線程安全隱患爷贫。

至于為什么认然?由于篇幅問題补憾,我們在后面一篇文章中利用測試工具進行詳細的分析(這可能也是面試中面試官會問到的問題)。大家現(xiàn)在只需要知道簡單的懶漢式會存在這么一個問題就行了卷员。

簡單懶漢式(線程安全)

通過對上面簡單懶漢式單例的測試盈匾,我們知道存在線程安全隱患,那么毕骡,如何來避免或者解決呢缝彬?

我們都知道 java 中有一個synchronized可以來對共享資源進行加鎖,保證在同一時刻只能有一個線程拿到該資源掸哑,其他線程只能等待蔗草。所以,我們對上面的簡單懶漢式進行改造叙凡,給getInstance() 方法加上synchronized

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午10:55
 */
public class LazySimpleSyncSingleton {
    private LazySimpleSyncSingleton() {
    }

    private static LazySimpleSyncSingleton instance = null;

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

然后使用發(fā)令槍進行測試:

@Test
public void testSync(){
    try {
        ConcurrentExecutor.execute(() -> {
            LazySimpleSyncSingleton instance = LazySimpleSyncSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + " : " + instance);
        }, 5, 5);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

進行多輪測試劈伴,并觀察結(jié)果,發(fā)現(xiàn)能夠獲取同一個示例:

pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de

線程安全問題是解決了握爷,但是跛璧,用synchronized加鎖,在線程數(shù)量比較多情況下新啼,如果CPU分配壓力上升追城,會導(dǎo)致大批量線程出現(xiàn)阻塞,從而導(dǎo)致程序運行性能大幅下降燥撞。

那么座柱,有沒有一種更好的方式,既兼顧線程安全又提升程序性能呢叨吮?答案是肯定的辆布。

我們來看雙重檢查鎖的單例模式。

雙重檢查鎖懶漢式

上面的線程安全方式的寫法锋玲,synchronized鎖是鎖在 getInstance() 方法上,當(dāng)多個線程過來拿資源的時候盾碗,其實需要拿的不是getInstance()這個方法,而是getInstance()方法里面的instance 實例對象商架,而如果這個實例對象一旦被初始化之后,多個線程到達時赶袄,就可以利用方法中的 if (instance == null) 去判斷是否實例化饿肺,如果已經(jīng)實例化了就直接返回,就沒有必要再進行實例化一遍汰聋。所以對上面的代碼進行改造:

第一次改造:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午2:03
 */
public class LazyDoubleCheckSingleton {
    private LazyDoubleCheckSingleton() {
    }

    private static LazyDoubleCheckSingleton instance = null;

    public static LazyDoubleCheckSingleton getInstance() {
        // 這里判斷是為了過濾不必要的同步加鎖玄妈,因為如果已經(jīng)實例化了,就可以直接返回了
        if (instance == null) {
            // 如果未初始化酝锅,則對資源進行上鎖保護蟋字,待實例化完成之后進行釋放
            synchronized (LazyDoubleCheckSingleton.class) {
                instance = new LazyDoubleCheckSingleton();
            }
        }
        return instance;
    }
}

這種方法行不行苛聘?答案肯定是不行唱捣,代碼中雖然是將同步鎖添加到了實例化操作中,解決了每個線程由于同步鎖的原因引起的阻塞蛀序,提高了性能;但是重贺,這里會存在一個問題:

  • 線程X線程Y同時調(diào)用getInstance()方法,他們同時判斷instance == null,得出的結(jié)果都是為null,所以進入了if代碼塊了
  • 此時線程X得到CPU的控制權(quán) -> 進入同步代碼塊 -> 創(chuàng)建對象 -> 返回對象
  • 線程X執(zhí)行完成了以后隧出,釋放了鎖也搓,然后線程Y得到了CPU的控制權(quán)傍妒。同樣是 -> 進入同步代碼塊 -> 創(chuàng)建對象 -> 返回對象

所以我們明顯可以分析出來:LazyDoubleCheckSingleton 類返回了不止一個實例!所以上面的代碼是不行的患雇!大家可以自行測試,我這里就不進行測試了!

我們再進行改造庐舟,經(jīng)過分析滔岳,由于線程X已經(jīng)實例化了對象,在線程Y再次進入的時候,我們再加一層判斷不就可以解決 “這個” 問題嗎?確實如此,來看代碼:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午2:03
 */
public class LazyDoubleCheckSingleton {
    private LazyDoubleCheckSingleton() {
    }
    private static LazyDoubleCheckSingleton instance = null;
    public static LazyDoubleCheckSingleton getInstance() {
        // 這里判斷是為了過濾不必要的同步加鎖,因為如果已經(jīng)實例化了构舟,就可以直接返回了
        if (instance == null) {
            // 如果未初始化弹澎,則對資源進行上鎖保護,待實例化完成之后進行釋放(注意,可能多個線程會同時進入)
            synchronized (LazyDoubleCheckSingleton.class) {
                // 這里的if作用是:如果后面的進程在前面一個線程實例化完成之后拿到鎖,進入這個代碼塊,
                // 顯然苍蔬,資源已經(jīng)被實例化過了,所以需要進行判斷過濾
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

大家覺得經(jīng)過這樣改造是不是就完美了呢?在我們習(xí)慣性的“講道理”的思維模式看來圈盔,好像確實沒什么問題,但是煤伟,程序是計算機在執(zhí)行癌佩;什么意思呢木缝?

instance = new LazyDoubleCheckSingleton(); 這段代碼執(zhí)行的時候,計算機內(nèi)部并非簡單的一步操作围辙,也就是非原子操作我碟,在JVM中,這一行代碼大概做了這么幾件事情:

  1. instance 分配內(nèi)存
  2. 調(diào)用 LazyDoubleCheckSingleton 的構(gòu)造函數(shù)來初始化成員變量
  3. instance對象指向分配的內(nèi)存空間(執(zhí)行完這步 instance 就為非 null 了)

但是在 JVM 中的即時編譯器中存在指令重排序的優(yōu)化姚建;通俗的來說就是矫俺,上面的第二步和第三步的順序是不能保證的,如果執(zhí)行順序是 1 -> 3 -> 2 那么在 3 執(zhí)行完畢掸冤、2 未執(zhí)行之前厘托,被另外一個線程 A 搶占了,這時 instance 已經(jīng)是非 null 了(但卻沒有初始化)稿湿,所以線程 A 會直接返回 instance铅匹,然后被程序調(diào)用,就會報錯饺藤。

當(dāng)然包斑,這種情況是很難測試出來的,但是確實會存在這么一個問題涕俗,所以我們必須解決它罗丰,解決方式也很簡單,就是 j 將 instance 加上 volatile 關(guān)鍵字再姑。

所以相對較完美的實現(xiàn)方式是:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午2:03
 */
public class LazyDoubleCheckSingleton {
    private LazyDoubleCheckSingleton() {
    }

    private static volatile LazyDoubleCheckSingleton instance = null;

    public static LazyDoubleCheckSingleton getInstance() {
        // 這里判斷是為了過濾不必要的同步加鎖萌抵,因為如果已經(jīng)實例化了,就可以直接返回了
        if (instance == null) {
            // 如果未初始化元镀,則對資源進行上鎖保護绍填,待實例化完成之后進行釋放(注意,可能多個線程會同時進入)
            synchronized (LazyDoubleCheckSingleton.class) {
                // 這里的if作用是:如果后面的進程在前面一個線程實例化完成之后拿到鎖栖疑,進入這個代碼塊沐兰,
                // 顯然,資源已經(jīng)被實例化過了蔽挠,所以需要進行判斷過濾
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

測試代碼見文末說明

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

上面的雙重鎖檢查形式的單例,對于日常開發(fā)來說瓜浸,確實夠用了澳淑,但是在代碼中使用synchronized關(guān)鍵字 ,總歸是要上鎖插佛,上鎖就會存在一個性能問題杠巡。難道就沒有更好的方案嗎?別說雇寇,還真有氢拥,我們從類初始化的角度來考慮蚌铜,這就是這里所要說到的靜態(tài)內(nèi)部類的方式。

廢話不多說嫩海,直接看代碼:

/**
 *
 * @author eamon.zhang
 * @date 2019-09-30 下午2:55
 */
public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton() {
    }

    // 注意關(guān)鍵字final冬殃,保證方法不被重寫和重載
    public static final LazyInnerClassSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }

    private static class LazyHolder {
        // 注意 final 關(guān)鍵字(保證不被修改)
        private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
    }
}

進行多線程測試:

pool-1-thread-9 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2
pool-1-thread-6 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2
...

結(jié)果都是同一個對象實例。

結(jié)論

這種方式即解決了餓漢式的內(nèi)存浪費問題叁怪,也解決了synchronized 所帶來的性能問題

原理

利用的原理就是類的加載初始化順序:

  1. 當(dāng)類不被調(diào)用的時候审葬,類的靜態(tài)內(nèi)部類是不會進行初始化的,這就避免了內(nèi)存浪費問題奕谭;
  2. 當(dāng)有方法調(diào)用 getInstance()方法時涣觉,會先初始化靜態(tài)內(nèi)部類,而靜態(tài)內(nèi)部類中的成員變量是 final 的血柳,所以即便是多線程官册,其成員變量是不會被修改的,所以就解決了添加 synchronized 所帶來的性能問題

首先感謝也恭喜大家能夠看到這里难捌,因為我想告訴你膝宁,上面所有的單例模式似乎還存在一點小問題 —— 暴力破壞。解決這一問題的方式就是下面提到的枚舉類型單例栖榨。

至于緣由和為何枚舉能夠解決這個問題昆汹,同樣,篇幅原因婴栽,我將在后面單獨開一篇文章來說明满粗。


下面我們先來講講注冊式單例。

3愚争、注冊式(登記式)單例

注冊式單例又稱為登記式單例映皆,就是將每一個實例都登記到某一個地方,使用唯一的標識獲取實例轰枝。

注冊式單例有兩種寫法:一種為容器緩存捅彻,一種為枚舉登記。

先來看枚舉式單例的寫法鞍陨。

枚舉單例

廢話少說步淹,直接看代碼,我們先創(chuàng)建EnumSingleton 類:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午3:42
 */
public enum EnumSingleton {
    INSTANCE;

    private Object instance;

    EnumSingleton() {
        instance = new EnumResource();
    }

    public Object getInstance() {
        return instance;
    }

}

來看測試代碼:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午3:47
 */
public class EnumSingletonTest {

    @Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                EnumSingleton instance = EnumSingleton.INSTANCE;
                System.out.println(instance.getInstance());
            }, 10, 10);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

測試結(jié)果:

com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7

結(jié)果都一樣诚撵,說明枚舉類單例是線程安全的缭裆,且是不可破壞的;在 JDK 枚舉的語法特殊性寿烟,以及反射也為枚舉保駕護航澈驼,讓枚舉式單例成為一種比較優(yōu)雅的實現(xiàn)。

枚舉類單例也是《Effective Java》中所建議使用的筛武。

容器式單例

注冊式單例還有另外一種寫法缝其,利用容器緩存挎塌,直接來看代碼:

創(chuàng)建ContainerSingleton類:

/**
 * @author EamonZzz
 * @date 2019-10-06 18:28
 */
public class ContainerSingleton {
    private ContainerSingleton() {
    }

    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();

    public static Object getBean(String className) {
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object object = null;
                try {
                    object = Class.forName(className).newInstance();
                    ioc.put(className, object);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return object;
            } else {
                return ioc.get(className);
            }
        }
    }
}

測試代碼:

@Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                Object bean = ContainerSingleton
                        .getBean("com.eamon.javadesignpatterns.singleton.container.Resource");
                System.out.println(bean);
            }, 5, 5);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

測試結(jié)果:

com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f

容器式寫法適用于創(chuàng)建實例非常多的情況,便于管理内边。但是榴都,是非線程安全的。

其實 Spring 中也有相關(guān)容器史丹利的實現(xiàn)代碼假残,比如 AbstractAutowireCapableBeanFactory 接口

至此缭贡,注冊式單例介紹完畢。


五辉懒、拓展

ThreadLocal 線程單例

ThreadLocal 不能保證其創(chuàng)建的對象是唯一的阳惹,但是能保證在單個線程中是唯一的,并且在單個線程中是天生的線程安全眶俩。

看代碼:

/**
 * @author EamonZzz
 * @date 2019-10-06 21:40
 */
public class ThreadLocalSingleton {
    private ThreadLocalSingleton() {
    }

    private static final ThreadLocal<ThreadLocalSingleton> instance = ThreadLocal.withInitial(ThreadLocalSingleton::new);

    public static ThreadLocalSingleton getInstance() {
        return instance.get();
    }
}

測試程序:

@Test
public void test() {
    System.out.println("-------------- 單線程 start ---------");
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println("-------------- 單線程 end ---------");
    System.out.println("-------------- 多線程 start ---------");
    try {
        ConcurrentExecutor.execute(() -> {
            ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + " : " + singleton);

        }, 5, 5);
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("-------------- 多線程 end ---------");
}

測試結(jié)果:

-------------- 單線程 start ---------
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
-------------- 單線程 end ---------
-------------- 多線程 start ---------
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@2f540d92
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@3ef7ab4e
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@604ffe2a
pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@50f41c9f
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@40821a7a
-------------- 多線程 end ---------

從測試結(jié)果來看莹汤,我們不難發(fā)現(xiàn),在主線程中無論調(diào)用多少次颠印,獲得到的實例都是同一個纲岭;在多線程環(huán)境下,每個線程獲取到了不同的實例线罕。

所以止潮,在單線程環(huán)境中,ThreadLocal 可以達到單例的目的钞楼。這實際上是以空間換時間來實現(xiàn)線程間隔離的喇闸。

六、總結(jié)

單例模式可以保證內(nèi)存里只有一個實例询件,減少了內(nèi)存的開銷燃乍;可避免對資源的浪費。

單例模式看起來非常簡單宛琅,實現(xiàn)起來也不難刻蟹,但是在面試中卻是一個高頻的面試題。希望大家能夠徹底理解嘿辟。


本篇文章所涉及的源代碼:

github.com/eamonzzz

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舆瘪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子红伦,更是在濱河造成了極大的恐慌介陶,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件色建,死亡現(xiàn)場離奇詭異,居然都是意外死亡舌缤,警方通過查閱死者的電腦和手機箕戳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門某残,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人陵吸,你說我怎么就攤上這事玻墅。” “怎么了壮虫?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵澳厢,是天一觀的道長。 經(jīng)常有香客問我囚似,道長剩拢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任饶唤,我火速辦了婚禮徐伐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘募狂。我一直安慰自己办素,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布祸穷。 她就那樣靜靜地躺著性穿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雷滚。 梳的紋絲不亂的頭發(fā)上需曾,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機與錄音揭措,去河邊找鬼胯舷。 笑死,一個胖子當(dāng)著我的面吹牛绊含,可吹牛的內(nèi)容都是我干的桑嘶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼躬充,長吁一口氣:“原來是場噩夢啊……” “哼逃顶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起充甚,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤以政,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后伴找,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盈蛮,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年技矮,在試婚紗的時候發(fā)現(xiàn)自己被綠了抖誉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殊轴。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖袒炉,靈堂內(nèi)的尸體忽然破棺而出旁理,到底是詐尸還是另有隱情,我是刑警寧澤我磁,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布孽文,位于F島的核電站,受9級特大地震影響夺艰,放射性物質(zhì)發(fā)生泄漏芋哭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一劲适、第九天 我趴在偏房一處隱蔽的房頂上張望楷掉。 院中可真熱鬧,春花似錦霞势、人聲如沸烹植。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽草雕。三九已至,卻和暖如春固以,著一層夾襖步出監(jiān)牢的瞬間墩虹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工憨琳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留诫钓,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓篙螟,卻偏偏與公主長得像菌湃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子遍略,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容