本篇內容介紹了“java單例模式怎么定義”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
創新互聯公司長期為1000多家客戶提供的網站建設服務,團隊從業經驗10年,關注不同地域、不同群體,并針對不同對象提供差異化的產品和服務;打造開放共贏平臺,與合作伙伴共同營造健康的互聯網生態環境。為興安盟企業提供專業的做網站、成都做網站,興安盟網站改版等技術服務。擁有10余年豐富建站經驗和眾多成功案例,為您定制開發。
一、單例模式定義:
單例模式確保某個類只有一個實例,而且自行實例化并向整個系統提供這個實例。在計算機系統中,線程池、緩存、日志對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若干個打印機,但只能有一個Printer Spooler,以避免兩個打印作業同時輸出到打印機中。每臺計算機可以有若干通信端口,系統應當集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調用。總之,選擇單例模式就是為了避免不一致狀態,避免政出多頭。
1、經典餓漢式:
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
特點:程序啟動時加載,先加載類,再初始化靜態屬性,由于后面無法再對對象進行修改,從而實現線程安全,效率相對高一些。占用內存相對多一些。
缺點:如果這個類特別龐大,初始化時將會特別緩慢,還有就是如果我們用不到這個類,它仍然會創建出來,浪費了資源。
2、經典懶漢式:
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
特點:延時加載,節約了內存。效率相對低一些。利用同步塊實現線程安全。
缺點:synchronized關鍵字是一個重鎖(對象鎖),它會每次調用getInstance(),都要對對象上鎖,事實上,只有在第一次創建對象的時候需要加鎖,之后就不需要了。
3、懶漢式變種—雙重檢查結構(不加volatile關鍵字修飾):
package cn.hzy.creationPattern.singleton;
public class Singleton3 {
private static Singleton3 instance = null;
private Singleton3(){
}
public static Singleton3 getInstance(){
if (instance == null) {
synchronized (instance) {
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
}
特點:屬于懶漢式的變種,上面懶漢式的特點都有,但是這里優化了性能問題,沒有給getInstance()方法加鎖,而是只給instance = new Singleton3();加鎖,也就是說只在初始化的時候會加鎖,后面的訪問因為instance!=null,就不會加鎖。
缺點:乍一看這種模式既沒有線程安全問題,又保證了單例,貌似完美了,但是JVM在創建對象的時候有可能為了優化性能而進行指令重排,
看似簡單的一句 instance = new Singleton3(); JVM在創建對象的時候會有三個步驟:
1、給Singleton3分配一個內存空間
2、初始化Singleton3(也就是創建Singleton3對象)
3、將instance指向剛分配的內存空間地址
但是有可能JVM為了編譯的優化提高效率就有可能變成下面一種順序:
1、給Singleton3分配一個內存空間
2、將instance指向剛分配的內存空間地址
3、初始化Singleton3(也就是創建Singleton3對象)
其實這種情況在單線程情況下是毫無影響的,結果都一樣,但是如果在多線程情況下,就有可能導致錯誤。
比如:A、B兩個線程訪問getInstance()方法,A先進入第一個if判斷,然后進入synchronized塊,開始初始化Singleton3,由于發生了指令重排,將instance指向剛分配的內存空間地址(此時未創建Singleton3對象),在這個時候,B訪問getInstance()方法,B進入第一個if判斷,因為instance已經指向了一個存在的內存空間地址,即instance!=null,此時直接返回instance(未初始化),然后再調用的時候如果A還沒有初始化完畢那么就會報空指針錯誤。(概率很低)
解決方案:加上volatile關鍵字修飾,
volatile:
特性一:內存可見性,即線程A對volatile變量的修改,其他線程獲取的volatile變量都是最新的。
特性二:可以禁止指令重排序。
修改如下:將 private static Singleton3 instance = null; 改為 private static volatile Singleton3 instance = null;
4、靜態內部類:
package cn.hzy.creationPattern.singleton;
public class Singleton4 {
private Singleton4() {}
public static Singleton4 getInstance() {
return SingletonFactory.instance;
}
private static class SingletonFactory {
private static Singleton4 instance = new Singleton4();
}
}
特點:按特征也是屬于懶漢模式,因為只會在我們需要用的時候才會創建實例對象,這里通過構造函數私有化,使用內部類來維護單例的實現,因為JVM內部的機制能夠保證當一個類被加載的時候,這個類的加載過程是線程互斥的。這樣當我們第一次調用getInstance的時候,JVM能夠幫我們保證instance只被創建一次, 并且會保證把賦值給instance的內存初始化完畢,這樣我們就不用擔心Singleton3出現的問題。同時該方法也只會在第一次調用的時候使用互斥機制,這樣就解決了低性能問題。
缺點:貌似這個就完美了,但是靜態內部類也有著一個致命的缺點,就是傳參的問題,由于是靜態內部類的形式去創建單例的,故外部無法傳遞參數進去的。
5、枚舉:
public enum Singleton {
INSTANCE;
public void method() {
}
}
直接調用SingleTon.INSTANCE就是單例。
特點:創建枚舉默認就是線程安全的
優點:簡直不要太多,1、寫法簡單,對比上面的實例就能發現。2、可以防止反射攻擊。
針對上面的反射攻擊我這里簡單說一下:在上面的1、2、3、4種單例模式里面,如果不對構造函數做一些安全處理,我們可以很輕松通過反射拿到構造器并且創建不只一個實例對象,就不再是單例了。但是對于枚舉,即時你通過反射拿到構造器,在創建對象實例的時候也會報錯,因為枚舉是可以防止反射攻擊的。
怎么對構造函數做一些安全處理?
可以立一個flag,在創建一個對象實例后,改變flag的值,通過判斷拋出異常。
比如:
private static boolean flag = false;
private Singleton (){
synchronized (Singleton .class) {
if(false == flag){
flag = !flag;
} else {
throw new RuntimeException("單例模式正在被反射攻擊!!!");
}
}
}
通過在構造函數里面增加一個判斷來保證不被反射攻擊。
“java單例模式怎么定義”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注創新互聯網站,小編將為大家輸出更多高質量的實用文章!
當前題目:java單例模式怎么定義
網站地址:http://m.2m8n56k.cn/article14/jogcge.html
成都網站建設公司_創新互聯,為您提供企業網站制作、手機網站建設、外貿網站建設、服務器托管、營銷型網站建設、動態網站
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:[email protected]。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯