一個小的技術細節

喝水會長肉發表於2021-12-15

在學習過之前的《單例》之後,相信大家一定對單例有了很深的理解,對於雙重校驗鎖的單例實現大家一定都不陌生。

不知道大家有沒有關注過一個細節,那就是在雙重校驗鎖中的getInstance方法中,定義了一個區域性變數來接收Singleton的單例物件。程式碼實現如下:


public 
class 
Singleton 
{

    private static volatile Singleton instance = null ;
    private Singleton ( ) {
    }

    public static Singleton getInstance ( ) {
       Singleton temp =instance ; // 定義了一個區域性變數
        if ( null == temp ) { //對區域性變數進行非空判斷
            synchronized ( Singleton .class ) {
               temp = instance ;
                if ( null == temp ) {
                   temp = new Singleton ( ) ; //對區域性變數進行賦值
                   instance =temp ; //再將區域性變數賦值給單例物件
                }
            }
        }
        return instance ; //返回單例物件
    }
}

以上,便是一個雙重校驗鎖的程式碼,可以看到,在getInstance方法中定義了一個區域性變數temp,在操作過程中都是對這個臨時的區域性變數進行的操作,最後再賦值給真正的單例物件的。

在很多原始碼中,也都有類似的做法,如Spring中有以下程式碼:


private 
static volatile ReactiveAdapterRegistry sharedInstance
;


public static ReactiveAdapterRegistry getSharedInstance ( ) {
//vx耗:mbz_java_panlong 十年開發經驗程式設計師,免費解答,備註“MI”即可

   ReactiveAdapterRegistry registry = sharedInstance ;
    if (registry == null ) {
            synchronized ( ReactiveAdapterRegistry .class ) {
               registry = sharedInstance ;
                if (registry == null ) {
                   registry = new ReactiveAdapterRegistry ( ) ;
                   sharedInstance = registry ;
                }
            }
    }
    return registry ;
}

那麼,你知道為什麼要這麼做嗎?

這裡其實和volatile有關,我們知道,雙重校驗鎖單例為了避免發生指令重排,一定要使用volatile來定義單例物件。

其實如果大家對於volatile有深入理解的話,這個問題其實不難回答。為了保證共享變數在併發場景下的記憶體可見性,volatile變數的操作前後都會通過插入記憶體屏障來進行資料同步,即將執行緒的本地記憶體資料同步到主記憶體(或從主記憶體將資料同步到執行緒的本地記憶體)

而這個過程其實是有很大的損耗的,我們可以想辦法降低對於volatile變數的訪問次數,那就是通過定義區域性變數的方式。

因為區域性變數並不是共享的,所以不需要進行執行緒本地記憶體和主存之間的資料同步,操作效率就會很高。

所以,使用區域性變數,是一種效能提升的方式,可以減少主存與執行緒記憶體的拷貝次數。

(全文完)



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010294/viewspace-2847955/,如需轉載,請註明出處,否則將追究法律責任。

相關文章