歡迎光臨
每天分享高質量文章

面試官:不使用synchronized和lock,如何實現一個執行緒安全的單例?

單例,大家肯定都不陌生,這是Java中很重要的一個設計樣式。稍微瞭解一點單例的朋友也都知道實現單例是要考慮併發問題的,一般情況下,我們都會使用synchronized來保證執行緒安全。

那麼,如果有這樣一道面試題:不使用synchronized和lock,如何實現一個執行緒安全的單例? 你該如何回答?

C類應聘者:可以使用餓漢樣式實現單例。如:

  1. public class Singleton { 
  2.     private static Singleton instance = new Singleton();
  3.     private Singleton (){}
  4.     public static Singleton getInstance() {
  5.       return instance;
  6.     }
  7. }

還有部分程式員可以想到餓漢的變種:

  1. public class Singleton {
  2.     private Singleton instance = null;
  3.     static {
  4.         instance = new Singleton();
  5.     }
  6.     private Singleton (){}
  7.     public static Singleton getInstance() {
  8.         return this.instance;
  9.     }
  10. }

使用static來定義靜態成員變數或靜態程式碼,藉助Class的類載入機制實現執行緒安全單例。

面試官:除了這種以外,還有其他方式嗎?

B類應聘者:

除了以上兩種方式,還有一種辦法,就是透過靜態內部類來實現,程式碼如下:

  1. public class Singleton {
  2.     private static class SingletonHolder {
  3.         private static final Singleton INSTANCE = new Singleton();
  4.     }
  5.     private Singleton (){}
  6.     public static final Singleton getInstance() {
  7.         return SingletonHolder.INSTANCE;
  8.     }
  9. }

這種方式相比前面兩種有所最佳化,就是使用了lazy-loading。Singleton類被裝載了,但是instance並沒有立即初始化。因為SingletonHolder類沒有被主動使用,只有顯示透過呼叫getInstance方法時,才會顯示裝載SingletonHolder類,從而實體化instance。

面試官:除了這種以外,還有其他方式嗎?

A類應聘者:

除了以上方式,還可以使用列舉的方式,如:

  1. public enum Singleton {
  2.     INSTANCE;
  3.     public void whateverMethod() {
  4.     }
  5. }

這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件,可謂是很堅強的壁壘。

面試官:以上幾種答案,其實現原理都是利用藉助了類載入的時候初始化單例。即藉助了ClassLoader的執行緒安全機制。

所謂ClassLoader的執行緒安全機制,就是ClassLoader的loadClass方法在載入類的時候使用了synchronized關鍵字。也正是因為這樣, 除非被重寫,這個方法預設在整個裝載過程中都是同步的,也就是保證了執行緒安全。

所以,以上各種方法,雖然並沒有顯示的使用synchronized,但是還是其底層實現原理還是用到了synchronized。

面試官:除了這種以外,還有其他方式嗎?

A類應聘者:

還可以使用Java併發包中的Lock實現

面試官:本質上還是在使用鎖,不使用鎖的話,有辦法實現執行緒安全的單例嗎?

A+類面試者:

有的,那就是使用CAS。

CAS是項樂觀鎖技術,當多個執行緒嘗試使用CAS同時更新同一個變數時,只有其中一個執行緒能更新變數的值,而其它執行緒都失敗,失敗的執行緒並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。實現單例的方式如下:

  1. public class Singleton {
  2.     private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
  3.  
  4.     private Singleton() {}
  5.  
  6.     public static Singleton getInstance() {
  7.         for (;;) {
  8.             Singleton singleton = INSTANCE.get();
  9.             if (null != singleton) {
  10.                 return singleton;
  11.             }
  12.  
  13.             singleton = new Singleton();
  14.             if (INSTANCE.compareAndSet(null, singleton)) {
  15.                 return singleton;
  16.             }
  17.         }
  18.     }
  19. }

面試官:這種方式實現的單例有啥優缺點嗎?

A++類面試者:

用CAS的好處在於不需要使用傳統的鎖機制來保證執行緒安全,CAS是一種基於忙等待的演演算法,依賴底層硬體的實現,相對於鎖它沒有執行緒切換和阻塞的額外消耗,可以支援較大的並行度。

CAS的一個重要缺點在於如果忙等待一直執行不成功(一直在死迴圈中),會對CPU造成較大的執行開銷。

另外,如果N個執行緒同時執行到singleton = new Singleton();的時候,會有大量物件建立,很可能導致記憶體上限溢位。

面試官:你被錄取了!

已同步到看一看
贊(0)

分享創造快樂