做時時彩網(wǎng)站平臺有哪些淘寶指數(shù)查詢工具
?1 概述
? ? ? ? 本章的知識點非常重要。在單例模式與多線程技術相結合的過程中,我們能發(fā)現(xiàn)很多以前從未考慮過的問題。這些不良的程序設計如果應用在商業(yè)項目中將會帶來非常大的麻煩。本章的案例也充分說明,線程與某些技術相結合中,我們要考慮的事情會更多。在學習本章的過程中,我們只需要考慮一件事情,那就是:如果使單例模式與多線程結合時是安全、正確的。
2 單例模式與多線程
? ? ? ? 在標準的23個設計模式中,單例模式在應用中是比較常見的。但多數(shù)常規(guī)的該模式教學資料并沒有結合多線程技術進行介紹,這就造成在使用結合多線程的單例模式時會出現(xiàn)一些意外。
3 立即加載/餓漢模式
? ? ? ? 立即加載指的是,使用類的時候已經(jīng)將對象創(chuàng)建完畢。常見的實現(xiàn)辦法就是new實例化,也被稱為“餓漢模式”。
public class MyObject {//立即加載方法 == 餓漢模式private static MyObject object = new MyObject();private MyObject(){}public static MyObject getInstance(){return object;}}
public class MyThread extends Thread{@Overridepublic void run(){System.out.println(MyObject.getInstance().hashCode());}
}
public class Run1 {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}
? ? ? ? 控制臺打印的hashcode是同一個值,說明對象是一個,也就實現(xiàn)了立即加載型單例模式。此代碼為立即加載模式,缺點是不能有其他實例變量,因為getInstance()方法沒有同步,所以有可能出現(xiàn)非線程安全問題。
4 延遲加載/懶漢模式
? ? ? ? 延遲加載就是調用get()方法時,實例才被創(chuàng)建。常見的實現(xiàn)辦法就是在get()方法中進行new實例化,也被稱為“懶漢模式”。
4.1 延遲加載解析
? ? ? ? 先看下面一段代碼。
public class MyObject {private static MyObject object;public MyObject() {}public static MyObject getInstance(){if(object == null){object = new MyObject();}return object;}
}
public class MyThread extends Thread{@Overridepublic void run(){System.out.println(MyObject.getInstance().hashCode());}
}
public class Run1 {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}
?4.2 延遲加載的缺點
? ? ? ? ? ? ? ? 前面兩個實驗雖然使用“立即加載”和“延遲加載”實現(xiàn)了單例模式,但在多線程環(huán)境中,“延遲加載”示例中的代碼完全是錯誤的,根本不能保持單例的狀態(tài)。下面來看如何在多線程環(huán)境中結合錯誤的單例模式創(chuàng)建出多個實例的。?
public class MyObject {private static MyObject object;public MyObject() {}public static MyObject getInstance(){try {if(object == null){//模擬在創(chuàng)建對象之前做一些準備工作Thread.sleep(3000);object = new MyObject();}}catch (InterruptedException e){e.printStackTrace();}return object;}
}
public class MyThread extends Thread{@Overridepublic void run(){System.out.println(MyObject.getInstance().hashCode());}
}
public class Run1 {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}
? ? ? ? 控制臺打印3個不同的hashCode,說明創(chuàng)建了3個對象,并不是單例的。這就是“錯誤的單例模式”,如何解決呢?
4.3 延遲加載的解決方案?
? ? ? ? (1)聲明synchronzied關鍵字
? ? ? ? 既然多個線程可以同時進入getInstance()方法,只需要對getInstance()方法聲明synchronzied關鍵字即可。修改MyObject.java類。
public class MyObject {private static MyObject object;public MyObject() {}synchronized public static MyObject getInstance(){try {if(object == null){//模擬在創(chuàng)建對象之前做一些準備工作Thread.sleep(3000);object = new MyObject();}}catch (InterruptedException e){e.printStackTrace();}return object;}
}
? ? ? ? 此方法在加入同步synchronzied關鍵字后得到相同實例的對象,但運行效率很低。下一個線程想要取得 對象,必須等待上一個線程釋放完鎖之后,才可以執(zhí)行。那換成同步代碼塊可以解決嗎?
(2)嘗試同步代碼塊
? ? ? ? 修改MyObject.java類。
public class MyObject {private static MyObject object;public MyObject() {}public static MyObject getInstance(){try {synchronized (MyObject.class){if(object == null){//模擬在創(chuàng)建對象之前做一些準備工作Thread.sleep(3000);object = new MyObject();}}}catch (InterruptedException e){e.printStackTrace();}return object;}
}
? ? ? ? ?此方法加入同步synchronzied語句塊后得到相同實例對象,但運行效率也非常低,和synchronzied同步方法一樣是同步運行的。下面繼續(xù)更改代碼,嘗試解決這個問題。
(3)針對某個重要的代碼進行單獨的同步。
修改MyObject.java類。
public class MyObject {private static MyObject object;public MyObject() {}public static MyObject getInstance(){try {if(object == null){//模擬在創(chuàng)建對象之前做一些準備工作Thread.sleep(3000);synchronized (MyObject.class) {object = new MyObject();}}}catch (InterruptedException e){e.printStackTrace();}return object;}
}
? ? ? ? 此方法使同步synchronzied語句塊只對實例化對象的關鍵代碼進行同步。從語句的結構上講,運行效率卻是得到了提升,但遇到多線程的情況還是無法得到同一個實例對象。
(4)使用DCL雙檢查鎖機制
public class MyObject {private volatile static MyObject object;public MyObject() {}public static MyObject getInstance(){try {if(object == null){Thread.sleep(2000);synchronized (MyObject.class){if(object == null){object = new MyObject();}}}}catch (InterruptedException e){e.printStackTrace();}return object;}
}
?使用volatile修改變量object,使該變量在多個線程間可見,另外禁止 object = new MyObject()代碼重排序。object = new MyObject()包含3個步驟:
? ? ? ? 1、memory = allocate();//分配對象的內存空間
? ? ? ? 2、ctorInstance(memory);//初始化對象
? ? ? ? 3、object = memory;//設置instance指向剛分配的內存地址
JIT編譯器有可能將這三個步驟重新排序。
? ? ? ? 1、memory = allocate();//分配對象的內存空間
? ? ? ? 2、object = memory;//設置instance指向剛分配的內存地址
? ? ? ? 3、ctorInstance(memory);//初始化對象
這時,構造方法雖然還沒有執(zhí)行,但object對象已具有內存地址,即值不是null。當訪問object對象中的值時,是當前聲明數(shù)據(jù)類型的默認值。
創(chuàng)建線程類MyThread.java代碼如下。
public class MyThread extends Thread{@Overridepublic void run(){System.out.println(MyObject.getInstance().hashCode());}
}
? ? ?
public class Run1 {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}
??
? ? ? ? 可見,使用DCL雙檢查鎖成功解決了懶漢模式下的多線程問題。DCL也是大多數(shù)多線程結合單例模式使用的解決方案。
5 使用靜態(tài)內置類實現(xiàn)單例模式
? ? ? ? DCL可以解決多線程單例模式的非線程安全問題。還可以使用其他辦法達到同樣的效果。
public class MyObject {private static class MyObjectHandler{private static MyObject object = new MyObject();}public MyObject() {}public static MyObject getInstance(){return MyObjectHandler.object;}
}
public class MyThread extends Thread{@Overridepublic void run(){System.out.println(MyObject.getInstance().hashCode());}
}
public class Run1 {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}
6 使用static代碼塊實現(xiàn)單例模式
? ? ? ? 靜態(tài)代碼塊中的代碼在使用類的時候就已經(jīng)執(zhí)行,所以可以使用靜態(tài)代碼塊的這個特性實現(xiàn)單例模式。
public class MyObject {private static MyObject object = null;public MyObject() {}static {object = new MyObject();}public static MyObject getInstance(){return object;}
}
public class MyThread extends Thread{@Overridepublic void run(){for (int i = 0; i < 5; i++) {System.out.println(MyObject.getInstance().hashCode());}}
}
public class Run1 {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();MyThread t3 = new MyThread();t1.start();t2.start();t3.start();}
}