柚子快報(bào)激活碼778899分享:JavaEE--單例模式
柚子快報(bào)激活碼778899分享:JavaEE--單例模式
1.設(shè)計(jì)模式
設(shè)計(jì)模式是在軟件開發(fā)中解決常見問題的最佳實(shí)踐或方案。使用設(shè)計(jì)模式可以實(shí)現(xiàn)可重用代碼,幫助我們創(chuàng)建更靈活、可維護(hù)和可擴(kuò)展的代碼。而我們要介紹的單例模式就是設(shè)計(jì)模式中比較經(jīng)典的一種模式。
2.單例模式
單例模式就是單個(gè)實(shí)例,在整個(gè)進(jìn)程中的某個(gè)類,有且只有一個(gè)對象,這樣的對象就被稱為“單例”,那么如何保證這個(gè)類只有一個(gè)實(shí)例呢?這就需要編譯器來幫我們做一個(gè)強(qiáng)制的檢查,通過一些編程上的技巧,使編譯器可以自動發(fā)現(xiàn)代碼中是否含有多個(gè)實(shí)例,并且在嘗試創(chuàng)建多個(gè)實(shí)例時(shí),直接編譯報(bào)錯,從根本上保證對象是唯一實(shí)例,這樣的代碼就稱為單例模式。
單例模式的實(shí)現(xiàn)方式有很多,我們主要介紹“餓漢模式”與“懶漢模式”
?2.1 餓漢模式
唯一實(shí)例的創(chuàng)建時(shí)間非常早,在類加載的同時(shí)就創(chuàng)建實(shí)例(就像一個(gè)餓了很久的人,看到吃的就迫不及待地開始吃起來)
public class Singleton {
private static Singleton instance=new Singleton();//static成員在類加載時(shí)初始化
public static Singleton getInstance(){
return instance;
}
private Singleton(){
//類外的代碼在嘗試new的時(shí)候,需要調(diào)用構(gòu)造方法,由于構(gòu)造方法是私有的,無法調(diào)用,就會編譯出錯
//通過用private修飾構(gòu)造方法,有效地禁止了外部代碼創(chuàng)建該類的實(shí)例
}
}
class Hungry{
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
}
2.2 懶漢模式
不是在程序啟動的時(shí)候創(chuàng)建實(shí)例,而是在第一次使用的時(shí)候才去創(chuàng)建實(shí)例
public class Singleton1 {
private static Singleton1 instance = null;
public static Singleton1 getInstance() {//什么時(shí)候調(diào)用,什么時(shí)候創(chuàng)建
if(instance == null) {
instance = new Singleton1();
}
return instance;
}
private Singleton1(){
}
}
class Lazy{
Singleton1 singleton1 = Singleton1.getInstance();
Singleton1 singleton2 = Singleton1.getInstance();
}
3.是否線程安全
在單線程環(huán)境下,上述兩種模式的代碼都夠正常執(zhí)行,但是當(dāng)處于多線程環(huán)境中,有多個(gè)線程調(diào)用getInstance方法時(shí),這兩種模式會不會出現(xiàn)線程安全問題呢?
3.1 餓漢模式(線程安全)
在餓漢模式中,實(shí)例創(chuàng)建的時(shí)間是在java進(jìn)程啟動時(shí)(比main線程被調(diào)用還早),后續(xù)在代碼中創(chuàng)建線程一定比實(shí)例的創(chuàng)建更加晚,所以當(dāng)后續(xù)線程在執(zhí)行g(shù)etInstance方法時(shí),實(shí)例早就已經(jīng)創(chuàng)建完畢,而getInstance方法只是去讀取了這個(gè)創(chuàng)建好的實(shí)例的值,相當(dāng)于多個(gè)線程去讀取同一個(gè)變量,不存在線程安全問題
3.2 懶漢模式(線程不安全)及改進(jìn)
在懶漢模式的getInstance方法中,存在賦值(修改)操作,在多線程中就可能會線程安全問題,我們來舉個(gè)例子
由于線程是隨機(jī)調(diào)度,搶占式執(zhí)行的,當(dāng)t1線程執(zhí)行到任何一個(gè)指令,t2線程都有可能搶占到cpu上繼續(xù)執(zhí)行,圖中只是其中一種執(zhí)行順序,還會有其他執(zhí)行順序的可能。按照圖中的執(zhí)行順序來看,t1線程和t2線程都分別創(chuàng)建了一個(gè)實(shí)例(都進(jìn)行了new操作),當(dāng)實(shí)例對象的內(nèi)存數(shù)據(jù)較大時(shí),多加載一次內(nèi)存數(shù)據(jù)會有很大的時(shí)間開銷
那么如何改進(jìn)代碼呢?
1.使用synchronized關(guān)鍵字,將if和new打包成一個(gè)原子操作,
2.由于只有第一次調(diào)用getInstance方法才會存在線程安全問題,一旦實(shí)例創(chuàng)建完畢,在后續(xù)調(diào)用中進(jìn)行的只是讀操作,不存在線程安全問題,這時(shí)進(jìn)行加鎖,就有點(diǎn)多余了,并且加鎖操作本身也有一定開銷(可能會使線程阻塞),所以需要添加一個(gè)if語句判斷是否需要加鎖
3.在兩個(gè)if語句之間,synchronized會使線程阻塞,在阻塞過程中其他線程可能會修改instance的值,由于內(nèi)存可見性問題,該線程可能沒有感知到,所以需要給instance變量添加volatile關(guān)鍵字
4.可能會出現(xiàn)指令重排序問題,也需要給instance變量添加volatile關(guān)鍵字
public class Singleton1 {
private static volatile Singleton1 instance = null;
private static Object locker = new Object();
public static Singleton1 getInstance() {
if(instance == null) {//判定是否要加鎖
//實(shí)例化之前需要加鎖
// 實(shí)例化之后,線程已經(jīng)安全,無需加鎖
synchronized (locker) {
if (instance == null) {
instance = new Singleton1();
}
}
}
return instance;
}
private Singleton1(){
}
}
class Lazy{
Singleton1 singleton1 = Singleton1.getInstance();
Singleton1 singleton2 = Singleton1.getInstance();
}
柚子快報(bào)激活碼778899分享:JavaEE--單例模式
相關(guān)鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。