1
單例模式的日常應(yīng)用
我們在瀏覽BBS、SNS網(wǎng)站的時候,常常會看到“當(dāng)前在線人數(shù)”這樣的一項內(nèi)容。對于這樣的一項功能,我們通常的做法是把當(dāng)前的在線人數(shù)存放到一個內(nèi)存、文件或者數(shù)據(jù)庫中,每次用戶登錄的時候,,就會馬上從內(nèi)存、文件或者數(shù)據(jù)庫中取出,,在其基礎(chǔ)上加1后,,作為當(dāng)前的在線人數(shù)進行顯示,然后再把它保存回內(nèi)存,、文件或者數(shù)據(jù)庫里,,這樣后續(xù)登錄的用戶看到的就是更新后的當(dāng)前在線人數(shù);同樣的道理,,當(dāng)用戶退出后,,當(dāng)前在線人數(shù)進行減1的工作。所以,,對于這樣的一個需求,,我們按照面向?qū)ο蟮脑O(shè)計思想,可以把它抽象為“在線計數(shù)器”這樣一個對象,,具體實現(xiàn)如下:
Java代碼:
class OnlineCounter {
private int onlineCount = 0;
public OnlineCounter(){
,假如讀出來的數(shù)據(jù)是
this.onlineCount = 100;
//在用戶登錄后,,在線人數(shù)加
public void incCount(){
}
1
this.onlineCount--;
//保存在線人數(shù)
}
public int getCount(){
}
public static void main(String[] args) {
OnlineCounter onlineCounter = new OnlineCounter();
<FONT style="FONT-SIZE: 10pt" color="black" face=""">
System.out.println("在線人數(shù):<FONT style="FONT-SIZE: 10pt" color="black" face=""">" +onlineCounter.getCount());
System.out.println("在線人數(shù):<FONT style="FONT-SIZE: 10pt" color="black" face=""">" + onlineCounter.getCount());
System.out.println("在線人數(shù):<FONT style="FONT-SIZE: 10pt" color="black" face=""">" + onlineCounter.getCount());
}
}
.Net代碼:
class OnlineCounter{
private int onlineCount = 0;
public OnlineCounter(){
,假如讀出來的數(shù)據(jù)是
this.onlineCount = 100;
//在用戶登錄后,在線人數(shù)加
public void incCount(){
}
1
this.onlineCount--;
//保存在線人數(shù)
return onlineCount;
//獲取在線人數(shù)
return onlineCount;
//測試函數(shù)
OnlineCounter onlineCounter = new OnlineCounter();
<FONT style="FONT-SIZE: 10pt" color="black" face=""">
Console.WriteLine("在線人數(shù):<FONT style="FONT-SIZE: 10pt" color="black" face=""">" + onlineCounter.getCount());
Console.WriteLine("在線人數(shù):<FONT style="FONT-SIZE: 10pt" color="black" face=""">" + onlineCounter.getCount());
Console.WriteLine("在線人數(shù):<FONT style="FONT-SIZE: 10pt" color="black" face=""">" + onlineCounter.getCount());
}
<?php
class OnlineCounter {
private $onlineCount = 0;
public function __construct(){
,假如讀出來的數(shù)據(jù)是
$this->onlineCount = 100;
//在用戶登錄后,,在線人數(shù)加
public function incCount(){
}
1
$this->onlineCount--;
//保存在線人數(shù)
}
public function getCount(){
}
public static function execute() {
echo "在線人數(shù):<FONT style="FONT-SIZE: 10pt" color="black" face=""">" . $onlineCounter->getCount();
echo "在線人數(shù):<FONT style="FONT-SIZE: 10pt" color="black" face=""">" . $onlineCounter->getCount();
echo "在線人數(shù):<FONT style="FONT-SIZE: 10pt" color="black" face=""">" . $onlineCounter->getCount();
}
?>
運行結(jié)果如下:
在線人數(shù):101
在線人數(shù):class Singleton {
private static Singleton instance = null;
private Singleton (){
// 公開的靜態(tài)工廠方法
public static Singleton getInstance(){
instance = new Singleton();
return instance;
}
.Net代碼:
// 私有的靜態(tài)對象
" p="" style="TEXT-ALIGN: left; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal" align="left" font="" style="FONT-SIZE: 10pt" color="black" face="">
//私有的構(gòu)造方法
}
,返回此類的唯一實例
if (instance == null){
}
}
<?php
// 私有的靜態(tài)對象
" p="" style="TEXT-ALIGN: left; MARGIN: 0cm 0cm 0pt; mso-layout-grid-align: none" class="MsoNormal" align="left" font="" style="FONT-SIZE: 10pt" color="black" face="">
//私有的構(gòu)造方法
}
,返回此類的唯一實例
if(self::$instance == null){
}
}
?>
Singleton類含有一個instance的私有靜態(tài)變量,用來保存該類唯一的實例對象,,它對于外部對象是不可見的,只能通過getInstance方法才能獲得,。
Singleton類的構(gòu)造器是private私有的,,外部對象無法通過它的構(gòu)造器生成實例,也就是說外部程序試圖通過new操作符來創(chuàng)建實例是行不通的,,因此,,getInstance方法成為獲得Singleton類實例的唯一途徑。
getInstance方法的設(shè)計非常簡單,,它首先檢測instance變量是否已經(jīng)初始化,,如果沒有被初始化,,就創(chuàng)建一個實例保存到instance變量,最后返回這個實例,;如果這個實例已經(jīng)被初始化,,那么就直接返回這個實例。
getInstance方法的設(shè)計非常簡單,,它首先檢測instance變量是否已經(jīng)初始化,,如果沒有被初始化,就創(chuàng)建一個實例保存到instance變量,,最后返回這個實例,;如果這個實例已經(jīng)被初始化,那么就直接返回這個實例,。
單例模式的類圖
單例模式主要有3個特點,:
1,、單例類確保自己只有一個實例,。
2、單例類必須自己創(chuàng)建自己的實例,。
3,、單例類必須為其他對象提供唯一的實例。
3
安全的單例模式:雙重檢查鎖定機制
我們雖然實現(xiàn)了單例模式,,但是目前的解決辦法并不安全,,依然存在著一定的缺陷:
private static Singleton instance = null;
}
if(instance == null){
}
}
instance
= null;
private Singleton() {
}
public static Singleton getInstance(){
1,第instance
if (//位置n個線程到達
synchronized (this)
{
3,,任何時間只能有instance
== null)
4,,第instance
{
instance;
}
}
.Net代碼:
private static Singleton instance = null;
private Singleton (){
public static Singleton getInstance(){
1,第instance
//位置n個線程到達
{
3,,任何時間只能有instance
== null)
4,,第instance
{
}
}
}
instance = new Singleton()語句,instance對象,。
(5)線程B
進入synchronized(this)塊,,到達位置3,進而到達位置4,。由于instance,,這時候的<FONT
style="LINE-HEIGHT: 115%; FONT-SIZE: 10pt" color="black" face=""">instance只有一個。
我們通過兩次檢查instance是否被實例化來解決線程安全問題,,這種處理方式稱為雙重檢查鎖定機制(Double-checked locking),。還有另外一種解決線程安全的方法,就是把getInstance方法整體作為同步區(qū),,比如聲明為public
static synchronized Singleton getInstance(),,這種方式由于鎖定的區(qū)域過大,,特殊情況下會造成系統(tǒng)性能的下降,成為系統(tǒng)的性能瓶頸,。
雙重檢查鎖定機制不僅解決了線程安全問題,,而且把性能也處理得很不錯,看起來非常完美,。不幸的是我們應(yīng)該注意不要在java中使用雙重檢查鎖定機制,,由于Java編譯器和 JIT
的優(yōu)化的原因,系統(tǒng)無法保證我們期望的執(zhí)行次序,。雖然Java語法中的volatile修飾符可以強制屏蔽編譯器和 JIT
的優(yōu)化工作,,但它是一種非常脆弱的同步機制,比較難以控制,,所以建議盡量減少使用,。我們后面還提供了其它的一種實現(xiàn)方式。
4
單例模式的實現(xiàn)方式:懶漢單例類和餓漢單例類
單例模式的實現(xiàn)有多種方法,,常見的就有懶漢式單例類和餓漢式單例類,。我們前面介紹的實現(xiàn)方法就屬于懶漢式單例類。
l
懶漢式單例類
對于懶漢模式,,我們可以這樣理解:該單例類非常懶,,只有在自身需要的時候才會行動,從來不知道及早做好準(zhǔn)備,。它在需要對象的時候,,才判斷是否已有對象,如果沒有就立即創(chuàng)建一個對象,,然后返回,,如果已有對象就不再創(chuàng)建,立即返回,。
懶漢模式只在外部對象第一次請求實例的時候才去創(chuàng)建,。
l
餓漢式單例
對于餓漢模式,我們可以這樣理解:該單例類非常餓,,迫切需要吃東西,,所以它在類加載的時候就立即創(chuàng)建對象。
Java代碼:
final class Singleton {
//私有的唯一實例成員,在類加載的時候就創(chuàng)建好了單例對象
private static final Singleton instance = new Singleton();
//私有的構(gòu)造方法,避免外部創(chuàng)建類實例
private Singleton() {
}
//靜態(tài)工廠方法,返回此類的唯一實例
public static Singleton getInstance() {
return instance;
}
}
.Net代碼:
sealed class Singleton {
//私有的唯一實例成員,在類加載的時候就創(chuàng)建好了單例對象
private static readonly Singleton instance = new Singleton();
//私有的構(gòu)造方法,避免外部創(chuàng)建類實例
private Singleton() {
}
//靜態(tài)工廠方法,返回此類的唯一實例
public static Singleton getInstance() {
return instance;
}
}
使用Java中的final關(guān)鍵字和.Net中sealed關(guān)鍵字去修飾class,,目的是阻止派生子類,,而派生子類可能會導(dǎo)致實例不唯一。使用Java中的final關(guān)鍵字和.Net中readonly關(guān)鍵字去修飾變量,,就意味著只能在類初始化時或者在構(gòu)造器中分配該變量,。
我們對比一下懶漢模式和餓漢模式的優(yōu)缺點:
這兩種模式對于初始化較快,占用資源少的輕量級對象來說,,沒有多大的性能差異,,選擇懶漢式還是餓漢式都沒有問題,。但是對于初始化慢,占用資源多的重量級對象來說,,就會有比較明顯的差別了,。所以,對重量級對象應(yīng)用餓漢模式,,類加載時速度慢,,但運行時速度快;懶漢模式則與之相反,,類加載時速度快,,但運行時第一次獲得對象的速度慢。
這兩種模式對于初始化較快,,占用資源少的對象來說,,沒有多大的性能差異,但是對于初始化慢,,占用資源多的對象來說就會有比較明顯的差別了,。所以,對重量級對象應(yīng)用餓漢模式,,在類加載時需要較長時間,但運行時會有明顯的時間效率的提升,。對重量級對象應(yīng)用懶漢模式,,在類加載時很快,但至少第一次獲得對象時需要等待很長時間,。
從用戶體驗的角度來說,,我們應(yīng)該首選餓漢模式。我們愿意等待某個程序花較長的時間初始化,,卻不喜歡在程序運行時等待太久,,給人一種反應(yīng)遲鈍的感覺,所以對于有重量級對象參與的單例模式,,我們推薦使用餓漢模式,。
而對于初始化較快的輕量級對象來說,選用哪種方法都可以,。如果一個應(yīng)用中使用了大量單例模式,,我們就應(yīng)該權(quán)衡兩種方法了。輕量級對象的單例采用懶漢模式,,減輕加載時的負擔(dān),,縮短加載時間,提高加載效率,;同時由于是輕量級對象,,把這些對象的創(chuàng)建放在使用時進行,,實際就是把創(chuàng)建單例對象所消耗的時間分?jǐn)偟秸麄€應(yīng)用中去了,對于整個應(yīng)用的運行效率沒有太大影響,。
5
什么情況下使用單例模式
單例模式也是一種比較常見的設(shè)計模式,,它到底能帶給我們什么好處呢?其實無非是三個方面的作用:
第一,、控制資源的使用,,通過線程同步來控制資源的并發(fā)訪問;
第二,、控制實例產(chǎn)生的數(shù)量,,達到節(jié)約資源的目的。
第三,、作為通信媒介使用,,也就是數(shù)據(jù)共享,它可以在不建立直接關(guān)聯(lián)的條件下,,讓多個不相關(guān)的兩個線程或者進程之間實現(xiàn)通信,。
比如,數(shù)據(jù)庫連接池的設(shè)計一般采用單例模式,,數(shù)據(jù)庫連接是一種數(shù)據(jù)庫資源,。軟件系統(tǒng)中使用數(shù)據(jù)庫連接池,主要是節(jié)省打開或者關(guān)閉數(shù)據(jù)庫連接所引起的效率損耗,,這種效率上的損耗還是非常昂貴的,。當(dāng)然,使用數(shù)據(jù)庫連接池還有很多其它的好處,,可以屏蔽不同數(shù)據(jù)數(shù)據(jù)庫之間的差異,,實現(xiàn)系統(tǒng)對數(shù)據(jù)庫的低度耦合,也可以被多個系統(tǒng)同時使用,,具有高可復(fù)用性,,還能方便對數(shù)據(jù)庫連接的管理等等。數(shù)據(jù)庫連接池屬于重量級資源,,一個應(yīng)用中只需要保留一份即可,,既節(jié)省了資源又方便管理,。所以數(shù)據(jù)庫連接池采用單例模式進行設(shè)計會是一個非常好的選擇,。
在我們?nèi)粘J褂玫脑?span lang="EN-US">Windows中也有不少單例模式設(shè)計的組件,象常用的文件管理器,。由于Windows操作系統(tǒng)是一個典型的多進程多線程系統(tǒng),,那么在創(chuàng)建或者刪除某個文件的時候,,就不可避免地出現(xiàn)多個進程或線程同時操作一個文件的現(xiàn)象。采用單例模式設(shè)計的文件管理器就可以完美的解決這個問題,所有的文件操作都必須通過唯一的實例進行,,這樣就不會產(chǎn)生混亂的現(xiàn)象,。
再比如,每臺計算機可以有若干個打印機,,如果每一個進程或者線程都獨立地使用打印機資源的話,,那么我們打印出來的結(jié)果就有可能既包含這個打印任務(wù)的一部分,又包含另外一個打印任務(wù)的一部分,。所以,,大多數(shù)的操作系統(tǒng)最終為打印任務(wù)設(shè)計了一個單例模式的假脫機服務(wù)Printer Spooler,所有的打印任務(wù)都需要通過假脫機服務(wù)進行,。
實際上,,配置信息類、管理類,、控制類,、門面類、代理類通常被設(shè)計為單例類,。像Java的Struts,、Spring框架,.Net的Spring.Net框架,,以及Php的Zend框架都大量使用了單例模式,。
|