設計模式是一套被反覆使用,多數人知曉,通過分類編目的,代碼設計的總結,也能夠說是前人的智慧結晶。學習設計模式能讓咱們對一些應用場景使用相同的套路達到很好的效果,我會不定時更新一些本身對設計模式的理解的文章,從定義,實現,應用場景來講說設計模式,今天我要說的對象是單例模式
一,定義
什麼是單例模式,字面理解,這種設計模式的目的是在必定程度上保證對象在程序中只有一個,或者只被建立一次,之因此說在必定程度上是由於還有還有反射機制存在,若是不考慮反射的話,確實是能夠保證對象惟一性的,這裏咱們不考慮反射。
二,實現
那麼如何保證對象的惟一性呢,能夠從兩方面出發,第一,對象的建立,第二,對象的獲取。若是咱們只給目標類提供一個構造方法,而且把這個構造方法私有化(private),那麼就能夠保證對象沒法被建立了(注意,必定要私有化,有的同窗會想,我不寫構造方法是否是就能夠了,注意不寫是會報錯的,由於全部的類都必須從Object這個父類中繼承無參構造方法,有的同窗又會說,我沒寫也沒報錯啊,那是由於你的開發工具,或者工具框架默認給你建立了!),固然對象絕對的沒法建立是沒有意義的,對象都沒發建立了,那這個對象存在也不能發揮做用,因此咱們對外提供一個惟一的獲取對象的方法,因爲不能建立對象,因此這個方法必須是靜態的(static),而後保證獲取的這個對象是惟一的,就能夠達到咱們的目的。那麼,若是保證咱們提供的對象是惟一的呢,從實現方式來講,能夠把設計模式分爲3類,分別是餓漢式,懶漢式,登記式
1.餓漢式
把對象理解爲麪包,一個餓漢對面包的態度是怎樣的,確定是但願本身立刻就擁有面包,餓漢式的原理也是如此,在程序初始化的時候就建立對象。下面展現餓漢式的建立代碼
public class SingleCase{java
private static SingleCase singleCase=initCase();設計模式
private SingleCase(){}//私有化構造方法安全
private static SingleCase initCase(){
//這裏就能夠寫具體的對象初始化信息了
//因爲這裏是演示代碼,我就簡單的new一下
return new SingleCase();
}
public static SingleCase getInstance(){多線程
return singleCase;併發
}
}
2.懶漢式
一樣把對象理解爲麪包,一個懶漢的態度就不同了,由於很懶,在他不餓,或者說不須要的時候,他是不會去拿面吧的(建立對象),因此懶漢式的核心思想是:須要的時候再建立對象。下面演示懶漢式單例建立代碼,爲了讓看客們更容易看懂,咱們在餓漢式的基礎上作修改
public class SingleCase{框架
private static SingleCase singleCase=null;//這裏沒有調用建立對象的方法,因此初始化的時候的singleCase值是空的高併發
private SingleCase(){}工具
private static SingleCase initCase(){性能
return new SingleCase();學習
}
public static SingleCase getInstance(){
//相比於餓漢式,這裏多了一個判斷,若是singleCase是空的,就建立,若是singleCase不是空的,那就直接返回
if (singleCase==null){
return initCase();
}
return singleCase;
}
}
以上代碼就是懶漢式單例模式的實現方式,可是細心的看客可能發現了一個問題,就getInstance()方法若是被多線程訪問的話,可能致使建立多個對象。咱們知道,java是一門多線程語言,cpu的執行也並非連續的,假設一臺計算機同時有10個進程在運行,每一個進程3個線程,那總共就有30個線程,cpu的工做就是在這30個線程之間快速的切換,執行一下1號線程,再執行一下2號線程,再執行一下8號線程,再執行一下18號線程,再執行一下一號線程,這種切換機制是隨機的,在一秒鐘以內,可能有的線程被執行100次,也有的線程可能被執行10次。那麼再上面的判斷中,可能會出現某個線程剛執行判斷if (singleCase==null)還沒來得及執行initCase()方法cpu就切換到另一個線程,而這個線程也在執行if (singleCase==null)判斷,而這時候對象還未被建立,因此也會進入if後面的代碼塊,最終致使initCase()被執行兩次,也就是建立了2個對象,這顯然和咱們的初衷不和。爲了保證對象的性,讓方法只被執行一次,咱們這裏使用synchronized關鍵字,synchronized做用在靜態方法上,能夠保證在整個程序中方法只能同時被一個線程執行,這個線程在執行的時候會擁有這個方法的鎖,致使其餘線程沒法獲取鎖,也就沒法執行,當該線程執行完了以後,會釋放這個鎖,而後才能被其餘線程執行(關於synchronized關鍵字,若是有看客不清楚用法能夠在評論區留言,若是人數多的話我再開個單章講講這個關鍵字),下面展現加synchronized以後的代碼,只須要在原有的方法上直接加上去便可
public class SingleCase{
private static SingleCase singleCase=null;
private SingleCase(){}
private static SingleCase initCase(){
return new SingleCase();
}
//直接加載方法上
public synchronized static SingleCase getInstance(){
if (singleCase==null)
return initCase();
return singleCase;
}
}
以上寫法確實能夠解決高併發的時候建立出多個對象的現象,可是並不完美,由於加了synchronized以後,每次調用方法都會加鎖,形成阻塞,影響性能,而咱們其實僅僅只須要保證在singleCase==null建立對象的時候上鎖就能夠了,而大部分狀況是對象已經建立好了,直接獲取的,咱們不但願影響這種狀況的性能。那麼,咱們能夠把鎖放在肯定對象沒有被建立以後,代碼更改以下
class SingleCase{
private static SingleCase singleCase=null;
private SingleCase(){}
private static SingleCase initCase(){
return new SingleCase();
}
public static SingleCase getInstance(){
if (singleCase==null){
synchronized (SingleCase.class){//synchronized的用法還請各看客自行學習,或者評論區留言
if (singleCase==null){
singleCase=initCase();
}
}
}
return singleCase;
}
}
關於synchronized (SingleCase.class){}裏面的代碼塊又加了一個if (singleCase==null)的判斷,可能有看客會奇怪,爲何這裏還要加一次判斷,其實這裏跟上面說的同樣,第一次singleCase==null的判斷可能出如今多個線程中,致使多個線程進入判斷後面的代碼塊,雖然不能進入被synchronized鎖定的代碼塊,可是隻要有線程進入了第一次singleCase==null判斷以後的代碼塊,當上一個擁有synchronized鎖的線程執行完建立對象的代碼釋放鎖以後,當前線程會繼續執行synchronized鎖後面的代碼再次建立對象,而這裏再加一個判斷,就能夠解決這個問題
3.登記式
仍是把對象理解爲麪包,不一樣於懶漢式和餓漢式的單個麪包,登記式也能夠說是廚師式,由於它操做的是多種麪包,就像廚師同樣,他擁有不少麪包。那麼多個麪包怎麼存放呢?麪包櫃Map!就像咱們在店裏看到的麪包櫃同樣,每種麪包都有對應的名字,就是咱們這裏的key,一個名字對應一個麪包,一個key對應一個對象。爲了讓這個key被全部人熟知,登記式的能夠通常使用class.getName();咱們知道單例模式的第一設計原則就是私有化構造方法,想要在一個類中存儲多個對象,又不能經過new的方法,那只有一條路能夠走了,反射。登記式就是利用反射來建立對象,到這裏確定有看客會說,既然用到反射了,那麼不經過你的麪包櫃,我也能夠拿到麪包吧,的確如此,這也是我我的比較詬病登記式的一個地方。不過登記式在特殊的場景中能發揮極其強大的做用,好比Spring的bean容器!Spring的bean容器不徹底是登記式,不過實現方式上有不少相通的地方。登記式相比於懶漢式有更豐富的建立方法,也涉及線程安全問題,這裏不一一演示,請各位看客自行實驗,下面展現登記式單例的簡單建立方法
public class SingleCase2 {
private static Map singletonMap = new HashMap();
private SingleCase2() {}
public static Object getInstance(String className) throws Exception{
if (!singletonMap.containsKey(className)) {
singletonMap.put(className, Class.forName(className).newInstance());
}
return singletonMap.get(className);
}
}
三.應用場景 單例模式的應用場景在網上隨便搜索一下能出來幾百篇文章,大多長篇大論晦澀難懂,我喜歡用一些簡單的話說明一些簡單的道理。咱們在說何時用單例時不如先來想一想單例能實現的效果,首先單例能現實的效果是能保證程序中目標類的對象只有一個,那麼何時須要保證類的對象只有一個呢,好比,某個配置文件類的的對象,咱們確定但願每次訪問的時候配置信息是一致的,若是文件信息有更新的話,直接就能從對應的類的對象中讀取到,若是這個類的對象有多個,可能會形成信息不一致,或者更新不及時。另外,保存一些公共資源時,好比java的線程池,咱們但願對某個線程的數量有一個準確的限制,若是不適用單例,控制起來是否是就會很是麻煩。而後,單例的另外一個優勢,減小資源消耗,當對象須要被大量訪問的時候,是每次建立一個對象呢,仍是重複適用同一個對象,固然是使用同一個對象佔用的資源少,Spring就是這麼幹的。那麼又何時使用懶漢式,何時使用餓漢式呢,若是你的對象再 程序啓動以後立刻就要使用的,須要初始化時就建立對象,那麼毫無疑問選擇餓漢式,若是你的對象指不定何時用,又不或者可能根本用不到,那麼使用懶漢式可能比較經濟一點。至於登記模式,但從使用的時間來講,能夠根據須要設計成懶漢式或者餓漢式,例子中是懶漢式。固然,登記模式通常在複雜場景中發揮重要的做用,好比Spring,而咱們實際開發中運用較少。 ps:今天就說這麼多,若是你喜歡,請幫忙點贊,評論,轉發。你的的確定是我寫下去的動力