23中設計模式(1):單例模式

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

單例模式的設計要點

  • 構造方法私有化靠益。
  • 有指向自己實例的靜態(tài)私有引用疮鲫。
  • 有對外提供自身實例的靜態(tài)公有方法。

根據(jù)實例化對象時機的不同分為三種:

一種是餓漢式單例昏苏,一種是懶漢式單例,還有一種是枚舉實現(xiàn)威沫,它是餓漢式單例的一種特殊情況贤惯。

  • 餓漢式單例,在單例類被加載時候棒掠,就實例化一個對象交給自己的引用孵构。

    /**
     * 餓漢式單例(可以使用)
     *
     * @author suvue
     * @date 2020/1/9
     */
    public class HungryStyle {
        private static HungryStyle instance = new HungryStyle();
    
        private HungryStyle() {
        }
    
        public static HungryStyle getInstance() {
            return instance;
        }
    }
    
  • 懶漢式單例,是指在調用取得實例方法的時候才會實例化對象烟很。其實懶漢模式的單例創(chuàng)建有很多種颈墅,這里列舉推薦使用的中l(wèi)兩種方式蜡镶。

    雙重檢索方式

    結合了其余方式做了改進,其他方式的缺點如將同步鎖加到getInstance()方法上恤筛,會導致速率很慢官还;而不進行雙重檢索(也就是不進行第二次校驗),就會有線程安全問題毒坛,假如一個線程進入了if (singleton == null)判斷語句塊望伦,還未來得及往下執(zhí)行,另一個線程也通過了這個判斷語句煎殷,這時便會產(chǎn)生多個實例屯伞。

    /**
     * 懶漢式單例(雙重檢索,推薦使用)
     *
     * @author suvue
     * @date 2020/1/9
     */
    public class DoubleCheckStyle {
        //volatile的使用是為了防止指令重排豪直。
        private static volatile DoubleCheckStyle instance = null;
    
        private DoubleCheckStyle() {
        }
    
        public static DoubleCheckStyle getInstance() {
            if (instance == null) {
                synchronized (DoubleCheckStyle.class) {
                    if (instance == null) {
                        //new對象的過程可拆解為三個過程:
                        //1.為新對象分配內存空間
                        //2.將變量引用的指針指向內存地址劣摇。
                        //3.實例化對象的一系列過程
                        //假如一個線程執(zhí)行了1、2弓乙,還沒來得及執(zhí)行3饵撑,這時為它分配的執(zhí)行時間
                        //用完了,另一個線程進來唆貌,此時因為上一個線程執(zhí)行了2滑潘,那么它會以為已經(jīng)
                        //實例化好對象了,就開心的拿著這個單例對象執(zhí)行操作去了锨咙,實際上只分配了
                        //內存空間和引用语卤,而沒有進行實例化,所以這個線程用的時候就會拋異常酪刀。
                        instance = new DoubleCheckStyle();
                    }
                }
            }
            return instance;
        }
    
    }
    

    靜態(tài)內部類方式

    這種方式和餓漢式單例一樣粹舵,都是采用類加載機制,但是不同的是骂倘,餓漢式在類加載時進行初始化眼滤,靜態(tài)內部類方式在調用getInstance方法時才會實例化。

    優(yōu)點:兼顧了懶漢模式的內存優(yōu)化(使用時才初始化)以及餓漢模式的安全性(類的靜態(tài)屬性只會在第一次加載類的時候初始化历涝,所以JVM幫助我們保證了線程的安全性)诅需。

    缺點:需要兩個類去完成這一實現(xiàn),雖然不會創(chuàng)建靜態(tài)內部類的對象荧库,但是其 Class 對象還是會被創(chuàng)建堰塌,而且是屬于永久帶的對象。因此創(chuàng)建好的單例分衫,一旦在后期被銷毀场刑,不能重新創(chuàng)建。

    /**
     * 懶漢式單例(通過靜態(tài)內部類的方式實現(xiàn)蚪战,推薦使用)
     *
     * @author suvue
     * @date 2020/1/9
     */
    public class StaticInnerStyle {
        private StaticInnerStyle() {
    
        }
    
        private static class InnerInstance {
            private final static StaticInnerStyle INSTANCE = new StaticInnerStyle();
        }
    
        public static StaticInnerStyle getInstance() {
            return InnerInstance.INSTANCE;
        }
    }
    
  • 枚舉式單例借助JDK1.5中添加的枚舉來實現(xiàn)單例模式牵现。不僅能避免多線程同步問題铐懊,而且還能防止反序列化重新創(chuàng)建新的對象。

    package cn.suvue.discipline.practice.designpattern.singleton;
    
    /**
     * 枚舉方式實現(xiàn)的單例
     *
     * @author suvue
     * @date 2020/1/9
     */
    public class EnumStyle {
        private EnumStyle() {
        }
    
        private enum Singleton {
            /**
             * 單例
             */
            INSTANCE;
            private final EnumStyle instance;
    
            Singleton() {
                this.instance = new EnumStyle();
            }
    
            private EnumStyle getInstance() {
                return instance;
            }
        }
    
        public static EnumStyle getInstance() {
            return Singleton.INSTANCE.getInstance();
        }
    }
    
    

單例模式的優(yōu)點

  • 在內存中只有一個對象瞎疼,節(jié)省內存空間科乎。
  • 避免頻繁的創(chuàng)建銷毀對象,可以提高性能丑慎。

使用注意事項

  • 只能使用單例類提供的方法得到單例對象喜喂,不要使用反射,否則將會實例化一個新對象竿裂。我們的代碼在反射面前就是裸奔的玉吁,它是一種非常規(guī)操作。

  • 構造方法時私有的腻异,因此單例類不可被繼承进副。

  • 多線程使用單例使用共享資源時,注意線程安全問題悔常。

  • 防止反射對單例造成破壞的方法影斑,因為反射是基于構造方法拿到的實例,所以我們可以這么改一下:

    private StaticInnerStyle() {
            if (getInstance()!=null){
                throw new RuntimeException("調用失敗");
            }
        }
    

單例模式在spring中應用

spring中使用的單例模式的懶加載机打,但是使用的是單例注冊表實現(xiàn)的矫户,先來看一個小例子。

package cn.suvue.discipline.practice.designpattern.singleton;


import java.util.concurrent.ConcurrentHashMap;
/**
 * 注冊表實現(xiàn)的單例
 *
 * @author suvue
 * @date 2020/1/9
 */
public class RegisterStyle {
    private static ConcurrentHashMap<String, Object> register = new ConcurrentHashMap<String, Object>(32);

    static {
        RegisterStyle res = new RegisterStyle();
        register.put(res.getClass().getName(), res);
    }

    private RegisterStyle() {
    }

    public static RegisterStyle getInstance(String name) {
        if (name == null) {
            name = "cn.suvue.discipline.practice.designpattern.singleton.RegisterStyle";
        }
        if (register.get(name) == null) {
            try {
                register.put(name, Class.forName(name).newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return (RegisterStyle) register.get(name);
    }
}

上述的代碼很簡單残邀,實現(xiàn)思路大同小異皆辽,唯一的不同是用到了ConcurrentHashMap。下面我們來看一下Spring中的應用芥挣。最經(jīng)典的就是BeanFactory的獲取bean的時候驱闷。

@SuppressWarnings("unchecked")
    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

        //校驗bean名是否有非法字符
        final String beanName = transformedBeanName(name);
        Object bean;

        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            //...
            //獲取bean的實例
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }

        else {
            //...

            try {
                //獲取并檢查bean的定義
                final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                checkMergedBeanDefinition(mbd, beanName, args);

                //...

                // 創(chuàng)建bean的實例 類定義是單例的情況.
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            //出錯了要銷毀bean
                            destroySingleton(beanName);
                            throw ex;
                        }
                    });
                    //獲取bean的實例
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }

                //多例情況 這里不多分析
                else if (mbd.isPrototype()) {
                     //...
                }

                //作用域相關代碼,這里不看它
                //...
            }
            catch (BeansException ex) {
                cleanupAfterBeanCreationFailure(beanName);
                throw ex;
            }
        }

        // Check if required type matches the type of the actual bean instance.
        if (requiredType != null && !requiredType.isInstance(bean)) {
            //校驗bean的類型是否與實際的相匹配
            //...
        }
        return (T) bean;
    }

上面是我簡化過的代碼空免,我們著重看下是單例情況空另,也就是getSingleton方法的具體實現(xiàn)。

/** 單例對象的緩存容器蹋砚,key是bean的名稱扼菠,value是bean的實例 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);


public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                //...
                boolean newSingleton = false;
                //...
                try {
                    //這里實際上創(chuàng)建一個新的對象
                    //因為這里的singletonFactory
                    //實際上傳進來的是createBean(beanName, mbd, args)
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                catch (IllegalStateException ex) {
                    //創(chuàng)建實例因為容器中已經(jīng)存在了,就拋出異常都弹,然后到容器中直接取單例對象
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        throw ex;
                    }
                }
                //...
                if (newSingleton) {
                    //如果是新的實例對象娇豫,那么就添加到容器中
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

下面我們看看spring是怎么往容器中放單例對象的。

protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            //直接在同步代碼塊 執(zhí)行put操作
            //上段代碼的getSingleton方法 用了一個synchronized
            //本段代碼中addSingleton方法 也用了一個synchronized
            //并且鎖的對象都是singletonObjects
            this.singletonObjects.put(beanName, singletonObject);
            //下面的代碼可以不用看畅厢,重要的是上面這行
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }

以上是我的一些粗鄙觀點,希望看到本博文的大神能糾正其中的錯誤氮昧!萬分感謝哦框杜,以后我對單例有了新的認知了浦楣,會不斷來補充更新的,也希望大神們多提提意見咪辱!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末振劳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子油狂,更是在濱河造成了極大的恐慌历恐,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件专筷,死亡現(xiàn)場離奇詭異弱贼,居然都是意外死亡,警方通過查閱死者的電腦和手機磷蛹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門吮旅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人味咳,你說我怎么就攤上這事庇勃。” “怎么了槽驶?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵责嚷,是天一觀的道長。 經(jīng)常有香客問我掂铐,道長罕拂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任堡纬,我火速辦了婚禮聂受,結果婚禮上,老公的妹妹穿的比我還像新娘烤镐。我一直安慰自己蛋济,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布炮叶。 她就那樣靜靜地躺著碗旅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪镜悉。 梳的紋絲不亂的頭發(fā)上祟辟,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音侣肄,去河邊找鬼旧困。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的吼具。 我是一名探鬼主播僚纷,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拗盒!你這毒婦竟也來了怖竭?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤陡蝇,失蹤者是張志新(化名)和其女友劉穎痊臭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體登夫,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡广匙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了悼嫉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艇潭。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖戏蔑,靈堂內的尸體忽然破棺而出蹋凝,到底是詐尸還是另有隱情,我是刑警寧澤总棵,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布鳍寂,位于F島的核電站,受9級特大地震影響情龄,放射性物質發(fā)生泄漏迄汛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一骤视、第九天 我趴在偏房一處隱蔽的房頂上張望鞍爱。 院中可真熱鬧,春花似錦专酗、人聲如沸睹逃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沉填。三九已至,卻和暖如春佑笋,著一層夾襖步出監(jiān)牢的瞬間翼闹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工蒋纬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留猎荠,地道東北人坚弱。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像法牲,于是被迫代替她去往敵國和親史汗。 傳聞我的和親對象是個殘疾皇子琼掠,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容

  • 摘要:設計模式之一:單例模式目錄介紹1.單例模式介紹2.單例模式定義3.單例模式使用場景4.單例模式的實現(xiàn)方式 4...
    肆虐的悲傷閱讀 461評論 0 2
  • 【學習難度:★☆☆☆☆拒垃,使用頻率:★★★★☆】直接出處:單例模式梳理和學習:https://github.com/...
    BruceOuyang閱讀 675評論 1 2
  • 單例模式(Singleton Pattern)是眾多設計模式中較為簡單的一個,同時它也是面試時經(jīng)常被提及的問題瓷蛙,如...
    廖少少閱讀 564評論 0 1
  • 認識“簡書”是一個好姐妹的每日精彩的更文吸引了我悼瓮,記得剛注冊“簡書”的時候,每天在浩瀚的文海里逛著艰猬,貪婪的讀著大量...
    Cherry櫻桃丸子閱讀 401評論 0 9
  • 天氣瀲滟晴朗横堡,還是陰晴不定,生活安穩(wěn)輕易冠桃,還是苦難波折命贴。請你相信,一切都是需要經(jīng)歷的食听,一切都是最好的安排胸蛛。 201...
    夢歸來閱讀 483評論 4 8