如何寫出面試官欣賞的Java單例

單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。通過單例模式可以保證系統中一個類只有一個實例。

今天我們不談單例模式的用途,只說一說如果在面試的時候面試官讓你敲一段代碼實現單例模式的情況下怎樣寫出讓面試官眼前一亮的單例代碼。因爲筆者學的是Java,所以接下來的實例將用Java語言編寫。

說到單例模式,第一個想到的是該類中有一個初始化爲null的自身引用,且被private修飾符修飾,其它類不得直接訪問。除此之外,單例模式的類還需要有private的構造方法,這一點不難理解,如果構造方法是public的,那麼類外部可以直接調用該類的構造方法,如此一來便不具備單例的特性。那麼怎麼獲取該類唯一的實例呢?這就需要一個公有的獲取器,該方法返回值類型是單例模式類,返回的結果自然是該類中唯一的實例。思路有了,我們便可以實現最簡單的單例模式類:

如何寫出面試官欣賞的Java單例

不得不說,這樣的做法確實達到了單例模式的要求,正常情況下系統中只有一個Singleton的對象。但是如果存在併發的情況呢?兩個用戶同時訪問該類的獲取器,此時假設Singleton對象還未被實例化,那麼系統將會兩次調用構造方法,這樣一來系統中就會存在兩個Singleton類的實例。說明這種方式的單例沒有考慮到併發情況,說明面試者只是粗略的瞭解單例模式,並沒有加以深入思考,想讓面試官滿意?呵呵。。。。。。

Java相較於C++而言個人認爲編程的難易度上來說要容易很多。在考慮線程同步時一個synchronized關鍵字便能解決普通加鎖問題。synchronized關鍵字,當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多隻有一個線程執行該段代碼。也就是說當兩個線程同時訪問類中synchronized方法或代碼塊時,只能有一個線程執行其代碼,另一個只能等待當前線程調用結束後才能訪問。這下子單例的實現就so easy了!只要對代碼稍加改動即可:

如何寫出面試官欣賞的Java單例這樣的寫法面試官會覺得你這個面試者在思考問題的時候比較全面,考慮到併發的情況,相較之前的方式面試官會覺得:少年,很有前途哦!

然而光是讓面試官看好是不夠的,我們要讓他欣賞,通過單例這樣的小問題便能拿到offer。也就是說第二種實現方式是可以進行優化的。如何優化呢?我們看到,當前系統中每次調用獲取方法時便會進行加鎖,而加鎖需要的時間便是我們可以進行優化的地方。現在我所想的是我們只需要在第一次調用時加一次鎖往後便再也不不需要加鎖了,這樣一來便省下了每次調用加鎖的時間,雖然計算機執行加鎖的時間很短但久而久之也是相當長的一段時間。

那麼怎麼實現呢?這需要引入另一個關鍵字volatile。volatile修飾的話就可以確保instance = new Singleton();對應的指令不會重排序(JVM當發現代碼執行順序變化但結果不變時可能會改變執行順序來提升自身性能。好坑。。。),也是線程安全的。

如何寫出面試官欣賞的Java單例

如何理解呢?

  1. 線程 1 進入get 方法。

  2. 由於single 爲null,線程 1 在 //1 處進入 synchronized塊。

  3. 線程 1 被線程 2 預佔。

  4. 線程 2 進入get 方法。

  5. 由於single 仍舊爲null,線程 2 試圖獲取 //1 處的鎖。然而,由於線程 1 持有該鎖,線程 2 在 //1 處阻塞。

  6. 線程 2 被線程 1 預佔。

  7. 線程 1 執行,由於在 //2 處實例仍舊爲null,線程 1 還創建一個Singleton對象並將其引用賦值給single。

  8. 線程 1 退出 synchronized塊並從 get方法返回實例。

  9. 線程 1 被線程 2 預佔。

  10. 線程 2 獲取 //1 處的鎖並檢查single 是否爲null。

  11. 由於single 是非 null的,並沒有創建第二個Singleton對象,由線程 1 創建的對象被返回。

像這樣的程序,面試官看完還能不給你offer嗎?騷年,前途不可限量啊!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章