1. 【設(shè)計模式】秒懂單例模式

        共 6188字,需瀏覽 13分鐘

         ·

        2022-07-04 17:34

        單例模式介紹

        概述

        單例模式:某一個類在系統(tǒng)中只需要有一個實例對象,而且對象是由這個類自行實例化并提供給系統(tǒng)其它地方使用,這個類稱為單例類。單例模式是GOF 23種設(shè)計模式中最簡單的一種,但同時也是在項目中接觸最多的一種。單例模式屬于一種創(chuàng)建型設(shè)計模式。

        使用場景

        大家都使用過Windows任務(wù)管理器,正常情況下,無論我們在Windows任務(wù)欄的右鍵菜單上點擊啟動多少次“任務(wù)管理器”,系統(tǒng)始終只能彈出一個任務(wù)管理器窗口。也就是說,在一個Windows系統(tǒng)中,系統(tǒng)只維護(hù)一個任務(wù)管理器。這就是一個典型的單例模式運(yùn)用。

        再舉一個例子,網(wǎng)站的計數(shù)器,一般也是采用單例模式實現(xiàn),如果你存在多個計數(shù)器,每一個用戶的訪問都刷新計數(shù)器的值,這樣的話你的實計數(shù)的值是難以同步的。但是如果采用單例模式實現(xiàn)就不會存在這樣的問題,而且還可以避免線程安全問題。同樣多線程的線程池的設(shè)計一般也是采用單例模式,這是由于線程池需要方便對池中的線程進(jìn)行控制。

        可以看出,我們在程序中使用單例模式,目的一般是處理資源訪問的沖突,或者從業(yè)務(wù)概念上,有些數(shù)據(jù)在系統(tǒng)中只應(yīng)保存一份,那也比較適合設(shè)計為單例類,比如配置類、全局流水號生成器等。

        UML類圖 

        單例模式實現(xiàn)

        單例模式實現(xiàn)要點

        單例模式雖然簡單,但是要寫出一個能保證在多線程環(huán)境下也能保證實例唯一性的單例確不是那么簡單,實現(xiàn)一個正確的單例模式有以下幾個要點:

        • ? 1.某個類只能有一個實例,即使是多線程運(yùn)行環(huán)境下;

        • ? 2.單例類的實例一定是單例類自身創(chuàng)建,而不是在單例類外部用其它方式如new方式創(chuàng)建;

        • ? 3.單例類需要提供一個方法向整個系統(tǒng)提供這個實例對象。

        單例兩種模式

        單例模式分為餓漢模式懶漢模式,這兩種模式很好理解,懶漢模式的意思就是這個類很懶,只要別人不找它要實例,它都懶得創(chuàng)建。餓漢模式在初始化時,我們就創(chuàng)建了唯一的實例,即便這個實例后面并不會被使用。

        下面分別介紹兩種單例模式的寫法。

        懶漢式

        下面這種寫法的單例是大家最簡單最容易寫出的一種單例寫法,只適用于單線程的系統(tǒng),也就是說它不是線程安全的。

        //懶漢式,線程不安全
         class Singleton1{
             private static  Singleton1 instance;

             //構(gòu)造函數(shù)定義為私有,防止外部創(chuàng)建實例
             private Singleton1(){

             }

             //系統(tǒng)使用單例的入口
             public static Singleton1 getInstance(){
                 if (null == instance){
                     instance = new Singleton1();
                 }

                 return instance;
             }
         }

        針對線程不安全的問題,可以通過獲取實例的方法添加了synchronized來解決,如下:

        //懶漢式,線程安全,效率低
        class Singleton2{
            private static  Singleton2 instance;

            //構(gòu)造函數(shù)定義為私有,防止外部創(chuàng)建實例
            private Singleton2(){

            }

            //系統(tǒng)使用單例的入口
            public static 
        synchronized Singleton2 getInstance(){
                if (null == instance){
                    instance = new Singleton2();
                }

                return instance;
            }
        }

        這樣一來,確實線程安全了,但是又帶來了另一個問題:程序的性能極大的降低了,高并發(fā)下多個線程去獲取這個實例,現(xiàn)在卻要排隊。

        針對性能問題,有同學(xué)想到了減小synchronized的粒度,不加在方法上,而是放在代碼塊中:

        //懶漢式,線程不安全
        class Singleton3{
            private static  Singleton3 instance;

            //構(gòu)造函數(shù)定義為私有,防止外部創(chuàng)建實例
            private Singleton3(){

            }

            //系統(tǒng)使用單例的入口
            public static Singleton3 getInstance(){
                if (null == instance){
                    
        synchronized(Singleton3.class) {
                        instance = new Singleton3();
                    }
                }

                return instance;
            }
        }

        但是,很不幸,如果改成這樣,又變得線程不安全了,我們試著分析一個代碼執(zhí)行的場景:假設(shè)我們有兩個線程 T1與T2并發(fā)訪問getInstance方法。當(dāng)T1執(zhí)行完if (instance == null)且instance為null時,其CUP執(zhí)行時間被T2搶占,所以T1還沒有創(chuàng)建實例。T2也執(zhí)行if (instance == null),此時instance肯定還為null,T2執(zhí)行創(chuàng)建實例的代碼,當(dāng)T1再次獲得CPU執(zhí)行時間后,其從synchronized 處恢復(fù),又會創(chuàng)建一個實例。

        那么有沒有一種寫法,可以同時兼顧到效率和線程安全兩方面了,還真有,就是我們下面將要介紹的double-check的方式。

        ////懶漢式,線程安全,效率還可以
        class Singleton4{
             //注意加上volatile關(guān)鍵字
            private static  volatile Singleton4 instance;

            //構(gòu)造函數(shù)定義為私有,防止外部創(chuàng)建實例
            private Singleton4(){

            }

            //系統(tǒng)使用單例的入口
            public static Singleton4 getInstance(){
                //第一次檢查提高訪問性能
             
           if (null == instance){
                    synchronized(Singleton4.class) {
                        //第二次檢查為了線程安全
                        
        if(instance ==null) {
                            instance = new Singleton4();
                        }
                    }
                }

                return instance;
            }
        }

        這種單例的寫法做了兩次 if (null == instance)的判斷,因此被稱為double-check的方式。

        • 第一次check為了提高訪問性能。因為一旦實例被創(chuàng)建,后面線程的所有的check都為假,不需要執(zhí)行synchronized競爭鎖了。

        • 第二次check是為了線程安全,確保多線程環(huán)境下只生成一個實例。

        需要注意的是,這種方式,在定義實例時一定需要加上volatile 關(guān)鍵字,禁止虛擬機(jī)指令重排,否則,還是有一定幾率會生成多個實例,關(guān)于volatile 關(guān)鍵字和指令重排的問題這里不過多介紹,后面在多線程安全系列文章中再詳細(xì)介紹。

        餓漢式

        使用靜態(tài)常量在類加載時候就創(chuàng)建了實例,屬于餓漢模式。其是線程安全的,這一點由JVM來保證。

        //餓漢式,線程安全
        class Singleton5{
             //
            
        private static final Singleton5 INSTANCE = new Singleton5();

            //構(gòu)造函數(shù)定義為私有,防止外部創(chuàng)建實例
            private Singleton5(){

            }

            //系統(tǒng)使用單例的入口
            public static Singleton5 getInstance(){
                return INSTANCE;
            }
        }

        本文源碼地址:
        https://github.com/qinlizhong1/javaStudy/tree/master/DesignPattern/src/singleton

        本文示例代碼環(huán)境:
        操作系統(tǒng):macOs 12.1
        JDK版本:12.0.1
        maven版本: 3.8.4

        推薦閱讀:

        完全整理 | 365篇高質(zhì)技術(shù)文章目錄整理

        算法之美 : 棧和隊列

        主宰這個世界的10大算法

        徹底理解cookie、session、token

        淺談什么是遞歸算法

        專注服務(wù)器后臺技術(shù)棧知識總結(jié)分享

        歡迎關(guān)注交流共同進(jìn)步

        瀏覽 44
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
          
          

            1. 粉嫩精品国产色综合久久不8 | av午夜福利 | 日本亚洲中文字幕 | 毛片网站在线观看 | 黄色短篇小说在线观看 |