线程安全的单例模式
單例模式,即我們只允許一個類有且僅有一個實例對外提供服務。通常為了性能考慮,單例模式會以懶加載的形式創建,也即單例中的懶漢模式,與之相對的當然就是單例的餓漢模式:不管用不用,我可以先給你創建出來。
1.單線程下的單例模式實現
非多線程環境下,由于不存在線程對共享數據(對象)的競爭,所以也就沒有線程安全問題,其單例模式實現如下:
public final class FooSingleton {//持有類的唯一實例private static FooSingleton instance = null;/*** 私有構造函數*/private FooSingleton() {//do nothing}/*** 提供外部獲取實例的工廠方法* 非線程安全* @return*/private static FooSingleton getInstance(){if (null == instance) {//非線程安全instance = new FooSingleton();}return instance;}public void methodOther() {//類提供的實例方法,調用方獲取單例對象后,可調用類中的實例方法}}2.多線程下的單例模式實現
可以發現,在多線程環境下,這個獲取單例的方法不是線程安全的:
private static FooSingleton getInstance(){if (null == instance) {//非線程安全instance = new FooSingleton();}return instance;}這就會導致客戶端(也就是各個調用端)獲取到的對象并不是同一個對象,這顯然就違背了我們使用單例模式的初衷了。怎樣保證線程安全呢?常用的實現方法有如下三種:
2-1 直接加鎖實現線程安全的單例模式
直接加鎖實現線程安全的單例模式:
public final class FooSingletonSafety {//持有類的唯一實例private static FooSingletonSafety instance = null;/*** 私有構造函數*/private FooSingletonSafety() {//do nothing}/*** 提供外部獲取實例的工廠方法* 線程安全:加鎖實現* synchronized保障了原子性、可見性和有序性* @return*/private static FooSingletonSafety getInstance(){synchronized (FooSingletonSafety.class) {if (null == instance) {instance = new FooSingletonSafety();}}return instance;}public void methodOther() {//類提供的實例方法,調用方獲取單例對象后,可調用類中的實例方法}}顯然,直接加鎖是可以保證單例模式的線程安全的,但是你可能會發現,這種實現方式下,每個線程在獲取單例對象的時候都要去搶鎖,獲取之后再釋放鎖,這顯然效率是不高的(每次獲取對象都要有鎖開銷)。所以,一般我們會通過下面的DCL去實現線程安全的單例模式
2-2 DCL實現線程安全的單例模式
DCL,即double-check-locking雙重檢查鎖定,代碼實現如下:
public final class FooSingletonBasedDCL {//持有類的唯一實例private static volatile FooSingletonBasedDCL instance = null;/*** 私有構造函數*/private FooSingletonBasedDCL() {//do nothing}/*** 提供外部獲取實例的工廠方法* 線程安全:DCL實現* synchronized保障了原子性、可見性和有序性* volatile保證校驗時(null == instance) instance對各個線程是可見的,同時禁止JIT和處理器重排序* @return*/private static FooSingletonBasedDCL getInstance(){if (null == instance) {synchronized (FooSingletonSafety.class) {if (null == instance) {//new關鍵字對應三條指令:1.分配對象所需的存儲空間 2.實例化對象 3.將對象引用賦值給變量//這三條指令正常執行順序是1->2->3,但是JIT編譯器或處理器在執行時可能會將其優化為1->3->2//所以,為了保證instance的可見性以及禁止重排序,所以instance變量必須要用volatile修飾//否則,極端情況下,一個線程在判斷if (null == instance)時,instance不為空直接返回,但此時//instance可能是個半對象(對象并沒有經過構造方法初始化),這將會造成代碼錯誤instance = new FooSingletonBasedDCL();}}}return instance;}public void methodOther() {//類提供的實例方法,調用方獲取單例對象后,可調用類中的實例方法}}DCL你可能開發中基本沒用過,但是這種實現方式在很多開源框架如Spring、dubbo等中都有使用
2-3 利用類加載機制實現線程安全的單例模式
利用Java的類加載機制:加載->鏈接->初始化,我們可以輕易的實現線程安全的單例模式,這種單例模式形式最簡單,代碼實現如下:
public final class BarSingleton {//利用類初始化只會發生一次,創建線程安全的單例private static final BarSingleton INSTANCE = new BarSingleton();public static BarSingleton getInstance() {return INSTANCE;}public void methodOther() {//類提供的實例方法,調用方獲取單例對象后,可調用類中的實例方法} }總結
- 上一篇: 子承父业-C#继承
- 下一篇: HDU 1159 Common Subs