(六)單例模式與多線程時的安全問題以及解決辦法

單例模式:

首先明白單例模式是什麼,簡單來說,就是說多個線程獲取到的對象是同一個對象,只new了一次,那麼建立單例有兩種方式:html

1.當即加載:即在程序一開始就new了一個對象,以後用的時候直接進行獲取,這種通常是定義靜態對象,由於靜態對象會預加載。java

2.延遲加載:顧名思義,指在第一次用的時候才建立對象,除了第一次獲取之外的是直接獲取。多線程

因此,當咱們將單例模式和多線程結合,會有什麼問題呢?異步

以下是一個延遲加載和多線程的例子:ide

MyObject.java:延遲加載方式建立對象函數

package 第六章; public class MyObject { private static MyObject myObject; public MyObject(){ } public static MyObject getInstance(){ if(myObject == null){    //只有第一次建立對象
            myObject = new MyObject(); } return myObject; } }
View Code

MyThread.java:  輸出當前線程獲取到的對象的hashcodespa

package 第六章; public class MyThread extends Thread { public void run(){ System.out.println("線程"+Thread.currentThread().getName()+"@@@"+MyObject.getInstance().hashCode()); } }
View Code

test.java: 運行類,建立三個線程並運行,線程

package 第六章; public class test { public static void main(String[] args){ MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread(); MyThread myThread3 = new MyThread(); myThread1.start(); myThread2.start(); myThread3.start(); } }
View Code

運行結果以下:code

很明顯,咱們能夠看到三個線程獲取到的對象並不都是同一個,緣由很簡單,因爲在建立對象的代碼沒有加鎖,是異步的,因此有多個線程if條件判斷成立,new了多個對象,解決方法也很簡單,將相應的代碼或者方法變成同步的。以下, htm

MyObject.java:  sleep模擬建立過程

package 第六章; public class MyObject { private static MyObject myObject; public MyObject(){ } public static MyObject getInstance(){ synchronized (MyLock.lock) { try{ if (myObject == null) { //只有第一次建立對象 Thread.sleep(1000); myObject = new MyObject(); System.out.println("時間"+System.currentTimeMillis()); } }catch (InterruptedException e){ e.printStackTrace(); } return myObject; } } }
View Code

運行以後能夠看到實現了單例效果,對象只建立了一次

可是,這種方法很蠢,由於每一次一個線程想要獲取實例,就必須等待上一個線程以內的同步代碼塊執行完畢才行,這樣子效率很低,事實上,咱們只是想在第一次加載對象的時候使用同步,以後咱們都不須要同步,由於對象已經建立好了,異步直接獲取就行。

解決辦法:

DCL雙檢查機制:只同步建立對象的代碼塊,而且進行兩次檢查,防止屢次建立,

更改MyObject.java

package 第六章; public class MyObject { private static MyObject myObject; public MyObject(){ } public static MyObject getInstance(){ try{ if (myObject == null) {    //只有第一次建立對象
                    Thread.sleep(1000); synchronized (MyLock.lock) { if(myObject==null){ myObject = new MyObject(); System.out.println("時間" + System.currentTimeMillis()); } } } }catch (InterruptedException e){ e.printStackTrace(); } return myObject; } }
View Code

我的感受仍是比較巧妙的吧,這樣子相比原來儘量的異步執行了更多的代碼塊。

固然也可使用enum,static代碼塊實現單例,由於枚舉類的構造函數是預加載的,static代碼塊也是預加載的,不過這種就是當即加載了,實現方法,

static代碼塊,將new對象的語句寫在static{}之中就ok

還有靜態內置類,可是這種在序列化和反序列化時候會出現必定的問題,須要用readResolve()方法,emmm,這塊沒太看懂想幹什麼,往後再說

原文出處:https://www.cnblogs.com/eenio/p/11394947.html

相關文章
相關標籤/搜索