平時(shí)上班比較忙,我就周末給大家更新下。今天給大家?guī)?lái)的是——單例模式。這個(gè)模式相對(duì)來(lái)說(shuō)比較簡(jiǎn)單一點(diǎn),但是有幾個(gè)點(diǎn)還是得說(shuō)一下,我們可以將這些細(xì)節(jié)用到我們自己的程序中。本來(lái)不想講的,但是我想把23種設(shè)計(jì)模式做成一個(gè)專題,以后大家不管是復(fù)習(xí)還是面試等,都可以有個(gè)參考。雖然寫(xiě)得并不好。
單例模式,聽(tīng)名字就能感覺(jué)出來(lái),就是只能有一個(gè)實(shí)例存在。的確如此,你的感覺(jué)很正確。既然只能存在一個(gè)實(shí)例,那么這個(gè)實(shí)例在什么時(shí)候創(chuàng)建呢?問(wèn)得好,我們看一下單例模式最常用的兩種分類。
一 懶漢式

用什么能表達(dá)我對(duì)你的心?唯有代碼,看下來(lái)。
public class SingleTask {
public static SingleTask mSingleTask = null;
private SingleTask(){};
public static SingleTask getNewInstance(){
if(null == mSingleTask){
mSingleTask = new SingleTask();
}
return mSingleTask;
}
}
二 餓漢式
public class SingleTask1 {
private static SingleTask1 mSingleTask1 = new SingleTask1();
private SingleTask1(){}
public static SingleTask1 getNewInstance(){
return mSingleTask1;
}
}
看吧,代碼是不是很簡(jiǎn)單。首先我們來(lái)看下什么是懶漢式,什么又是餓漢式。
懶漢式:顧名思義,很懶。不到最后一刻就不動(dòng)。在我們代碼里面就是到你用我的時(shí)候,我才創(chuàng)建,不用的時(shí)候,我就歇著。
餓漢式:顧名思義,饑渴。呸,換句話說(shuō),就是勤快吧。我在類初始化的時(shí)候就做好準(zhǔn)備,時(shí)刻迎接著使用者。
不管是,懶漢式還是餓漢式。通過(guò)代碼,我們都會(huì)發(fā)現(xiàn)一個(gè)特點(diǎn)。
- 在單例的類中,都會(huì)持有一個(gè)當(dāng)前類的對(duì)象;
- 構(gòu)造函數(shù)私有化;
- 提供一個(gè)訪問(wèn)該對(duì)象的靜態(tài)方法。
這么理解呢?
我們可以將單例模式視為————壟斷。對(duì),沒(méi)錯(cuò)就是壟斷。我不會(huì)讓你通過(guò)其他方法創(chuàng)建我,只能使用我自己創(chuàng)建的,所以私有化構(gòu)造函數(shù)。既然只有我能創(chuàng)建,那么我會(huì)為你提供一個(gè)方法,讓你通過(guò)合法途徑得到這個(gè)對(duì)象,而不是通過(guò)歪門(mén)邪道。
好了,現(xiàn)在我們來(lái)看下不同的地方。
首先餓漢式?jīng)]啥說(shuō)的,我們忽略。主要看一下懶漢式的代碼。
public static SingleTask getNewInstance(){
if(null == mSingleTask){
mSingleTask = new SingleTask();
}
return mSingleTask;
}
我們這么做,會(huì)不會(huì)有什么問(wèn)題?
問(wèn)題當(dāng)然會(huì)有,在單線程模式下當(dāng)然不會(huì)有問(wèn)題。在實(shí)際的問(wèn)題中,我們可能就不是單線程這么簡(jiǎn)單了,往往會(huì)涉及到多線程。在多線程的時(shí)候就會(huì)很容易出現(xiàn)問(wèn)題,會(huì)導(dǎo)致對(duì)象被重復(fù)創(chuàng)建,顯然,違背了設(shè)計(jì)單例的初衷。那么怎么解決呢?
等等,這個(gè)地方我不知道大家有沒(méi)有疑問(wèn)?多線程真的會(huì)出問(wèn)題嗎?(因?yàn)槲业牟┛偷亩ㄎ痪褪切率?,所以為了解除大家的疑?wèn),我寫(xiě)個(gè)代碼驗(yàn)證下,消除大家的疑慮)。
懶漢式的困擾一
public class SingleTask {
public static SingleTask mSingleTask = null;
private static int time = 0;
private SingleTask(){
System.out.println(time++);
};
public static SingleTask getNewInstance(){
if(null == mSingleTask){
mSingleTask = new SingleTask();
}
return mSingleTask;
}
public static void main(String[] args){
for(int i=0;i<100;i++){
new Thread(){
@Override
public void run(){
SingleTask.getNewInstance();
}
}.start();
}
}
}
我在構(gòu)造函數(shù)中,打印一句話,以此來(lái)證明確實(shí)被創(chuàng)建多次了。
讓我們看看結(jié)果:



我隨便運(yùn)行幾次,就已經(jīng)這樣了。證明代碼確實(shí)是有問(wèn)題的。那么如何改進(jìn)呢?
有人肯定會(huì)說(shuō),我知道,加鎖。好的,我們加鎖。
public static SingleTask getNewInstance() {
synchronized (SingleTask.class) {
if (null == mSingleTask) {
mSingleTask = new SingleTask();
}
}
return mSingleTask;
}
// 其他部分代碼不變的
來(lái)我們先運(yùn)行下,用事實(shí)說(shuō)話。

運(yùn)行到你手困,結(jié)果只是調(diào)用一次。啊,好高興啊,終于解決了。我們來(lái)分析下這段代碼?

假設(shè)現(xiàn)在有三個(gè)饑渴的線程A,B,C來(lái)拿東西了。想象一下畫(huà)面有助于增強(qiáng)印象。他們幾乎同時(shí)來(lái)到位置1??吹介T(mén)上有一把鎖,所以只能放一個(gè)進(jìn)去。他們商量了下,B進(jìn)去了(實(shí)際上誰(shuí)進(jìn)去并不確定)。B來(lái)到位置2,發(fā)現(xiàn)mSingleTask = null,所以來(lái)到位置3,創(chuàng)建對(duì)象咯。創(chuàng)建完走出去了。緊接著A,C兩個(gè)商量了下,C進(jìn)去了。發(fā)現(xiàn)mSingleTask已經(jīng)不為空了,很高興直接拿著跑了。最后剩下A,等待好久終于進(jìn)去了,和C一樣,偷完就跑真刺激。
問(wèn)題是解決了,本著處女座的原則。我們繼續(xù)分析下代碼看有沒(méi)有可以升華革命友誼的地方。
懶漢式的困擾二
既然是鎖,那么肯定會(huì)帶來(lái)一定的開(kāi)銷(xiāo),我們每次來(lái)的時(shí)候都要加鎖,這個(gè)代價(jià)可想而知。那么能有什么解決辦法嗎?
當(dāng)然,我是誰(shuí),我是不一樣的煙火。
public static SingleTask getNewInstance() {
if (null == mSingleTask) {
synchronized (SingleTask.class) {
if (null == mSingleTask) {
mSingleTask = new SingleTask();
}
}
}
return mSingleTask;
}
可以看到,我們加了雙重判空。這樣,只有在mSingleTask = null的情況下才會(huì)加鎖,然后苦苦等待對(duì)象創(chuàng)建完成。好了吧,當(dāng)mSingleTask被賦予了新的生命后就不需要再次加鎖。
三 靜態(tài)內(nèi)部類
public class SingleTask2 {
private static SingleTask2 mSingleTask2;
private SingleTask2(){
//System.out.println("new instance");
}
public static SingleTask2 getSingleInstance() {
return SingleTaskHolder.instance;
}
static class SingleTaskHolder {
// 內(nèi)部類初始化單例實(shí)例
private static SingleTask2 instance = new SingleTask2();
}
// 驗(yàn)證下
public static void main(String[] args){
for(int i=0;i<100;i++){
new Thread(){
@Override
public void run(){
SingleTask2.getSingleInstance();
}
}.start();
}
}
}
通過(guò)這種方式我們也能實(shí)現(xiàn)單例模式,那么這么做的原理是什么呢?
因?yàn)殪o態(tài)內(nèi)部類只會(huì)被加載一次,所以這么做也是線程安全的。
好了,聽(tīng)你吹了這么長(zhǎng)時(shí)間。這個(gè)模式有用嗎?使用場(chǎng)景舉個(gè)例子。

其他的不說(shuō),就說(shuō)一個(gè)Android中的Application
......
最后我們看一下較為正式的定義。
單例模式:保證一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn) (記住,是壟斷,只能通過(guò)我的途徑拿貨)。
好了,今天就到這兒,下期見(jiàn)!