1. 單例模式
????????單例模式是一種設(shè)計模式,設(shè)計模式是我們必須要掌握的一個技能;
1.1 關(guān)于框架和設(shè)計模式
????????設(shè)計模式是軟性的規(guī)定,且框架是硬性的規(guī)定,這些都是技術(shù)大佬已經(jīng)設(shè)計好的;
????????一般來說設(shè)計模式有很多種,且不同的語言會有不同的設(shè)計模式,(同時設(shè)計模式也可以理解為對編程語言的一種補(bǔ)充)
1.2 細(xì)說單例模式
????????單例 = 單個實例(對象);
????????某個類,在一個線程中,只應(yīng)該創(chuàng)建一個實例化對象(原則上不應(yīng)該有多個),這時就使用單例模式,如此可以對我們的代碼進(jìn)行一個更嚴(yán)格的校驗和檢查。
????????保證對象唯一性的方法:
????????方法一,可以通過“協(xié)議約束”,寫一個文檔,規(guī)定這個類只能有唯一的實例,程序員在接手這個代碼時,就會發(fā)現(xiàn)這個文檔已經(jīng)進(jìn)行約定,其中的規(guī)定約束著程序員在創(chuàng)建對象時,時刻注意只能創(chuàng)建一個對象。
????????方法二:從機(jī)器入手;讓機(jī)器幫我們檢查,我們期望讓機(jī)器幫我們對代碼中指定的類,創(chuàng)建類的實例個數(shù)進(jìn)行檢查、校驗,當(dāng)創(chuàng)建的實例個數(shù)超過我們期望個數(shù),就編譯報錯。其中單例模式就是已經(jīng)設(shè)計好的套路,可以實現(xiàn)這種預(yù)期效果。
? ? ? ? 關(guān)于單例模式代碼實現(xiàn)的基本方式有兩種:餓漢模式和懶漢模式;
2. 餓漢模式
????????餓漢模式是指創(chuàng)建實例的時期非常早;在類加載的時候,程序一啟動,就已經(jīng)創(chuàng)建好實例了,使用 “餓漢”這個詞,就是形容創(chuàng)建實例非常迫切,非常早。單例模式代碼如下:
class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
private Singleton(){ }
}
public class TestDemo4 {
public static void main(String[] args) {
Singleton singleton = new Singleton();
}
}
? ? ? ? 當(dāng)我們運(yùn)行該代碼時,系統(tǒng)就會報錯,接下來我們詳細(xì)的分析一下此處的代碼;?
????????這樣,如果我們想new一個Singleton對象,也new不了,同時不管我們用getInstance獲取多少次實例,獲取的對象都是同一個對象,代碼如下:
package thread;
// 就期望這個類只能有唯一的實例 (一個進(jìn)程中)
class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
private Singleton() {}
}
public class ThreadDemo26 {
public static void main(String[] args) {
// Singleton s = new Singleton();
Singleton s = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s == s2);
}
}
? ? ? ? 結(jié)果如下:
3. 懶漢模式
????????和餓漢模式不一樣的是,懶漢模式創(chuàng)建實例的時機(jī)比較晚,沒餓漢創(chuàng)建實例那么迫切,只有第一次使用這個類時,才會創(chuàng)建實例,代碼如下:
class SingletonLazy {
private static SingletonLazy instance = null;
public static SingletonLazy getInstance() {
if(instance == null) {
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy() { }
}
public class TestDemo5 {
public static void main(String[] args) {
}
}
? ? ? ? 下面為代碼圖解分析:
????????和餓漢模式的區(qū)別就是沒那么迫切創(chuàng)建實例,等需要調(diào)用這個類的時候才創(chuàng)建一個實例,而餓漢模式是有了這個類就創(chuàng)建出實例。
????????懶漢模式的優(yōu)點:有的程序,要在一定條件下,才需要進(jìn)行相關(guān)的操作,有時候不滿足這個條件,也就不需要完成這個操作了,如此哦·就把這個操作省下來了。
4. 兩種模式關(guān)于線程安全
4.1 餓漢模式
? ? ? ? 線程安全;
? ? ? ? 對于餓漢模式來說,上圖所示通過調(diào)用getinstance方法來返回instance對象,本質(zhì)上來說是讀操作;
????????當(dāng)有多個線程,同時并發(fā)執(zhí)行,調(diào)用getInstance方法,取instance,這時線程是安全的,因為只涉及到讀,多線程讀取同一個變量,是線程安全的。而instance很早之前就已經(jīng)創(chuàng)建好了,不會修改它,一直也只有這一個實例,也不涉及寫的操作。
4.2?懶漢模式
? ? ? ? 線程不安全;
????????在懶漢模式中,條件判定和返回時是讀操作,new一個對象是寫操作;
? ? ? ?我們只有調(diào)用getInstance方法后,就會創(chuàng)建出實例來,如果多個線程同時調(diào)用這個方法,此時SingletonLazy類里面的instance都為null,那么這些線程都會new對象,就會創(chuàng)建多個實例。這時,就不符合我們單例模式的預(yù)期了,所以,這個代碼是線程不安全的。
????????線程不安全的直接原因,就是 “寫” 操作不是原子的。
4.3?解決懶漢模式的線程安全問題
4.3.1 把寫操作打包成原子
????????因為多線程并發(fā)執(zhí)行的時候,可能讀到的都是instance == null,所以會創(chuàng)建多個實例,那我們就給它加鎖,讓它在創(chuàng)建實例的時候,只能創(chuàng)建一個,加鎖代碼如下:
class SingletonLazy {
private static Object locker = new Object();
private static SingletonLazy instance = null;
public static SingletonLazy getInstance() {
synchronized (locker) {
if(instance == null) {
instance = new SingletonLazy();
}
}
return instance;
}
private SingletonLazy() { }
}
? ? ? ? 以上操作雖然將寫操作打包成了一個原子,但是新的問題也出現(xiàn)了;
4.3.2?去除冗余操作
?????????上述操作加上了還是有問題:如果已經(jīng)創(chuàng)建出實例了,我們還有加鎖來判斷它是不是null嗎,加鎖這些操作也是要消耗硬件資源的,沒有必要為此浪費資源空間,如果已經(jīng)不是null了,我們就想讓它直接返回,不再進(jìn)行加鎖操作,代碼修改如下:
class SingletonLazy {
private static Object locker = new Object();
private static SingletonLazy instance = null;
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (locker) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
private SingletonLazy() { }
}
? ? ? ? 代碼圖解分析兩個判斷語句的是目的意義:
4.3.3 指令重排序的問題
????????指令重排序:指令重排序也是編譯器的一種優(yōu)化,在保證原代碼的邏輯不變,調(diào)整原代碼的指令執(zhí)行順序,從而讓程序的執(zhí)行效率提高。
????????保證原代碼的邏輯不變,改變原有指令的順序,從而提高代碼的執(zhí)行效率,其中這個代碼,就存在著指令重排序的優(yōu)化,如下圖代碼:
該語句原本指令執(zhí)行順序:
????????1、去內(nèi)存申請一段空間
????????2、在這個內(nèi)存中調(diào)用構(gòu)造方法,創(chuàng)建實例
????????3、從內(nèi)存中取出地址,賦值給這個實例instance。
指令重排序后的順序:1, 3 , 2;按照指令重排序后的代碼執(zhí)行邏輯就變成了下面所示:
????????假設(shè)有兩個線程,現(xiàn)在執(zhí)行順序如下圖所示:
????????因為指令重排序后,先去內(nèi)存申請一段空間,然后是賦值給instance,那這時,instance就不是null了,第二個線程不會進(jìn)入到if語句了,直接返回instance,可是instance還沒有創(chuàng)建出實例,這樣返回肯定是有問題的,如此也就線程不安全了。
????????解決方案:
????????給instance這個變量,加volatile修飾,強(qiáng)制取消編譯器的優(yōu)化,不能指令重排序,同時也排除了內(nèi)存可見性的問題。
????????加volatile后的代碼如下:
?class SingletonLazy { private static Object locker = new Object(); private static volatile SingletonLazy instance = null; public static SingletonLazy getInstance() { if (instance == null) { synchronized (locker) { if (instance == null) { instance = new SingletonLazy(); } } } return instance; } private SingletonLazy() { } }
? ? ? ? 至此,我們才算解決掉懶漢模式關(guān)于線程安全的所有問題;
4.4 懶漢模式線程安全的代碼
package thread;
// 懶漢的方式實現(xiàn)單例模式.
class SingletonLazy {
// 這個引用指向唯一實例. 這個引用先初始化為 null, 而不是立即創(chuàng)建實例
private volatile static SingletonLazy instance = null;
private static Object locker = new Object();
public static SingletonLazy getInstance() {
// 如果 Instance 為 null, 就說明是首次調(diào)用, 首次調(diào)用就需要考慮線程安全問題, 就要加鎖.
// 如果非 null, 就說明是后續(xù)的調(diào)用, 就不必加鎖了.
if (instance == null) {
synchronized (locker) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
private SingletonLazy() { }
}
public class ThreadDemo27 {
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
System.out.println(s1 == s2);
}
}
? ? ? ? 結(jié)果如下:
文章來源:http://www.zghlxwxcb.cn/news/detail-774048.html
ps:本次的內(nèi)容就到這里了,如果感興趣的話,就請一鍵三連哦?。?!文章來源地址http://www.zghlxwxcb.cn/news/detail-774048.html
到了這里,關(guān)于【Java EE初階六】多線程案例(單例模式)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!