單例模式的優缺點
1、時間和空間
比較上面兩種寫法:懶漢式是典型的時間換空間,也就是每次獲取實例都會進行判斷,看是否需要創建實例,浪費判斷的時間。當然,如果一直沒有人使用的話,那就不會創建實例,則節約內存空間。
餓漢式是典型的空間換時間,當類裝載的時候就會創建類實例,不管你用不用,先創建出來,然后每次調用的時候,就不需要再判斷了,節省了運行時間。
2、線程安全
(1)從線程安全性上講,不加同步的懶漢式是線程不安全的,比如,有兩個線程,一個是線程A,一個是線程B,它們同時調用getInstance方法,那就可能導致並發問題。如下示例:
public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; }
程序繼續運行,兩個線程都向前走了一步,如下:
public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; }
(2)餓漢式是線程安全的,因為虛擬機保證只會裝載一次,在裝載類的時候是不會發生並發的。
(3)如何實現懶漢式的線程安全呢?
當然懶漢式也是可以實現線程安全的,只要加上synchronized即可,如下:
- public static synchronized Singleton getInstance(){}
但是這樣一來,會降低整個訪問的速度,而且每次都要判斷。那么有沒有更好的方式來實現呢?
(4)雙重檢查加鎖
可以使用"雙重檢查加鎖"的方式來實現,就可以既實現線程安全,又能夠使性能不受到很大的影響。那么什么是"雙重檢查加鎖"機制呢?
所謂雙重檢查加鎖機制,指的是:並不是每次進入getInstance方法都需要同步,而是先不同步,進入方法過后,先檢查實例是否存在,如果不存 在才進入下面的同步塊,這是第一重檢查。進入同步塊過后,再次檢查實例是否存在,如果不存在,就在同步的情況下創建一個實例,這是第二重檢查。這樣一來, 就只需要同步一次了,從而減少了多次在同步情況下進行判斷所浪費的時間。
雙重檢查加鎖機制的實現會使用一個關鍵字volatile,它的意思是:被volatile修飾的變量的值,將不會被本地線程緩存,所有對該變量的讀寫都是直接操作共享內存,從而確保多個線程能正確的處理該變量。
看看代碼可能會更加清楚些。示例代碼如下:
public class Singleton { /** * 對保存實例的變量添加volatile的修飾 */ private volatile static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ //先檢查實例是否存在,如果不存在才進入下面的同步塊 if(instance == null){ //同步塊,線程安全地創建實例 synchronized(Singleton.class){ //再次檢查實例是否存在,如果不存在才真正地創建實例 if(instance == null){ instance = new Singleton(); } } } return instance; } }
這種實現方式可以實現既線程安全地創建實例,而又不會對性能造成太大的影響。它只是在第一次創建實例的時候同步,以后就不需要同步了,從而加快了運行速度。
