1. 程式人生 > >面試中的單例問題

面試中的單例問題

有一種 復雜 當我 功能 枚舉 由於 nal 直接 如果

當我興沖沖的帶著筆記答案參加面試時,突然發現面前的面試官顯得很嚴肅而且眉頭緊鎖,不知道是工作太累了,還是說他對今天的面試官不是很滿意。

於是我就勇敢的坐過去在他的面前坐了下來,沒想到第一道題就讓面試官看出了我的水平,因此今天跟大家聊聊面試中單例的問題,希望大家都能了解這塊內容。

在早期的項目代碼中,如果我們想使用類的某個方法,我們基本都會創建一個類的對象實例然後再調用方法,這樣的實現往往在系統內就會存在某個類的大量實例。如此一來,項目框架很難管理大量的對象,而且如果java虛擬機不能及時回收,容易造成內存溢出。

首先我們要明白什麽是單例,所謂單例就是說在項目框架內某個類的對象實例只存在一個,任何調用方獲取到的對象實例都是一個,那麽很明顯這個類是不能夠被外部直接調用類構造器創建的。

我們先看下一個簡單的單例設計:

技術分享圖片

上面代碼在單線程是沒有問題的,而且只有當線程調用類的靜態方法時,才會生成類的靜態變量。但是當多線程訪問時,上面代碼是有問題的,會生成多個對象的實例。

那麽,我們可以用另外一種方法實現,比如說在類加載時候就初始化對象的實例,這樣後面無論怎麽調用類靜態方法都不創建新的實例。還有一種方法,但是會犧牲部分系統性能,意思就是在多線程訪問方法時通過鎖機制讓線程排隊訪問。我們先通過在類方法上加鎖來實現類的單例,比如:

技術分享圖片

上述方法能實現單例,而且采用的思路是延遲加載,但是執行效率比較低。

之前有看到部分同學使用雙重鎖(Double CheckLock)機制來實現單例模式,一方面需要在實例上加上volatile關鍵字通知操作系統實現線程訪問時內存屏障,然後還需要在方法中通過虛擬機實現的synchronized來同步方法訪問,寫法如下:

技術分享圖片

反正,我認為上面的實現是比較復雜的,大家需要去了解的知識點比較多,比如volatile ,synchronized,內存屏障。因此我不建議大家用這種方式,可以作為技術了解下還是有好處的,畢竟如果能跟面試官探討到這一步,還是會加一些分的。

如果說我們不考慮服務負載問題,在多線程環境下可以預先加載類的靜態實例,當虛擬機加載完成類後就會創建類的靜態變量,甭管你到時用不用,反正給你留在那裏。所有線程訪問到的都是同一靜態實例,有人也稱這種方式為餓漢式,具體寫法如下:

技術分享圖片

上面寫法實現單例也是沒有問題的,但是有些同學就會覺得如果我只是想調用一個類的某個靜態方法,並不想生成它的實例,那有沒有其他方法呢,經過各路大神的指點結合自身的總結,可以使用內部靜態類來實現這個需求。

開發的同學都知道,虛擬機在加載類的過程中一開始並不會初始化類的內部靜態類。如果線程調用內部靜態類時,虛擬機只會初始化一次,這樣既可以實現單例,同時也是線程安全的。具體寫法如下:

技術分享圖片

除了以上講到的幾種方式外,JDK自身的枚舉類型本身就是單例的實現,調用者不能顯式的調用構造器完成實例創建,因此很多Java規範文檔推薦使用枚舉來實現單例。

當然對於初級開發人員而言,現在的主流開發框架都提供單例/多例模式供開發者選擇,這樣的好處讓開發者更多關註業務功能開發,而不用過多關註虛擬機內部類實例創建問題。例如spring中默認類註入就是單例的,可以根據實際情況設置scope為singleton(單例)或者prototype(多例),如下圖所示:

技術分享圖片

由於本人技術水平還有很大的上升空間,文中存在講述不清楚或者錯誤的地方,請大家批評指正。

面試中的單例問題