av激情亚洲男人的天堂国语,日韩欧美精品一中文字幕,无码av一区二区三区无码,国产又色又爽又刺激的a片,国产又色又爽又刺激的a片

漫談設(shè)計模式-技術(shù)要點詳解

第3章 單例(Singleton)模式

山陰網(wǎng)站制作公司哪家好,找成都創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站設(shè)計等網(wǎng)站項目制作,到程序開發(fā),運營維護。成都創(chuàng)新互聯(lián)從2013年成立到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設(shè)就選成都創(chuàng)新互聯(lián)。

3.1 概述

如果要保證系統(tǒng)里一個類最多只能存在一個實例時,我們就需要單例模式。這種情況在我們應(yīng)用中經(jīng)常碰到,例如緩存池,數(shù)據(jù)庫連接池,線程池,一些應(yīng)用服務(wù)實例等。在多線程環(huán)境中,為了保證實例的唯一性其實并不簡單,這章將和讀者一起探討如何實現(xiàn)單例模式。

3.2 最簡單的單例

為了限制該類的對象被隨意地創(chuàng)建,我們保證該類構(gòu)造方法是私有的,這樣外部類就無法創(chuàng)建該類型的對象了;另外,為了給客戶對象提供對此單例對象的使用,我們?yōu)樗峁┮粋€全局訪問點,代碼如下所示:

 
 
  1. public class Singleton {   
  2.     private static Singleton instance = new Singleton();     
  3.     //other fields…   
  4.  
  5.     private Singleton() {   
  6.     }   
  7.  
  8.     public static Singleton getInstance() {   
  9.         return instance;   
  10.     }   
  11.      
  12.     //other methods…   
  13. }   

代碼注解:

l Singleton類的只有一個構(gòu)造方法,它是被private修飾的,客戶對象無法創(chuàng)建該類實例。

l 我們?yōu)榇藛卫龑崿F(xiàn)的全局訪問點是public static Singleton getInstance()方法,注意,instance變量是私有的,外界無法訪問的。

讀者還可以定義instance變量是public的,這樣把屬性直接暴露給其他對象,就沒必要實現(xiàn)public static Singleton getInstance()方法,但是可讀性沒有方法來的直接,而且把該實例變量的名字直接暴露給客戶程序,增加了代碼的耦合度,如果改變此變量名稱,會引起客戶類的改變。

還有一點,如果該實例需要比較復(fù)雜的初始化過程時,把這個過程應(yīng)該寫在static{…}代碼塊中。

l 此實現(xiàn)是線程安全的,當(dāng)多個線程同時去訪問該類的getInstance()方法時,不會初始化多個不同的對象,這是因為,JVM(Java Virtual Machine)在加載此類時,對于static屬性的初始化只能由一個線程執(zhí)行且僅一次[1]。

由于此單例提供了靜態(tài)的公有方法,那么客戶使用單例模式的代碼也就非常簡單了,如下所示:

Singleton singleton = Singleton.getInstance();

3.3 進階

3.3.1 延遲創(chuàng)建

如果出于性能等的考慮,我們希望延遲實例化單例對象(Static屬性在加載類是就會被初始化),只有在第一次使用該類的實例時才去實例化,我們應(yīng)該怎么辦呢?

這個其實并不難做到,我們把單例的實例化過程移至getInstance()方法,而不在加載類時預(yù)先創(chuàng)建。當(dāng)訪問此方法時,首先判斷該實例是不是已經(jīng)被實例化過了,如果已被初始化,則直接返回這個對象的引用;否則,創(chuàng)建這個實例并初始化,最后返回這個對象引用。代碼片段如下所示:

 
 
  1. public class UnThreadSafeSingelton {   
  2.     //variables and constructors…   
  3.  
  4.     public static UnThreadSafeSingelton getInstance() {   
  5.         if(instatnce ==null){   
  6.           instatnce = new UnThreadSafeSingelton();   
  7.         }   
  8.         return instatnce;   
  9.     }   
  10. }   

我們使用這句if(instatnce ==null) 判斷是否實例化完成了。此方法不是線程安全的,接下來我們將會討論。

3.3.2 線程安全

上節(jié)我們創(chuàng)建了可延遲初始化的單例,然而不幸的是,在高并發(fā)的環(huán)境中,getInstance()方法返回了多個指向不同的該類實例,究竟是什么原因呢?我們針對此方法,給出兩個線程并發(fā)訪問getInstance()方法時的一種情況,如下所示:

t1 t2

1 if(instatnce ==null)

2 if(instatnce ==null)

3 instatnce = new UnThreadSafeSingelton();

4 return instatnce;

5 instatnce = new UnThreadSafeSingelton()

6 return instatnce;

如果這兩個線程按照上述步驟執(zhí)行,不難發(fā)現(xiàn),在時刻1和2,由于還沒有創(chuàng)建單例對象,Thread1和Thread2都會進入創(chuàng)建單例實例的代碼塊分別創(chuàng)建實例。在時刻3,Thread1創(chuàng)建了一個實例對象,但是Thread2此時已無法知道,繼續(xù)創(chuàng)建一個新的實例對象,于是這兩個線程持有的實例并非為同一個。更為糟糕的是,在沒有自動內(nèi)存回收機制的語言平臺上運行這樣的單例模式,例如使用C++編寫此模式,因為我們認(rèn)為創(chuàng)建了一個單例實例,忽略了其他線程所產(chǎn)生的對象,不會手動去回收它們,引起了內(nèi)存泄露。

為了解決這個問題,我們給此方法添加synchronized關(guān)鍵字,代碼如下:

 
 
  1. public class ThreadSafeSingelton {   
  2.     //variables and constructors…   
  3.  
  4.     public static synchronized ThreadSafeSingelton getInstance() {   
  5.         if(instatnce ==null){   
  6.             instatnce = new ThreadSafeSingelton();   
  7.         }   
  8.         return instatnce;   
  9.     }   
  10. }   

這樣,再多的線程訪問都只會實例化一個單例對象。

3.3.3 Double-Check Locking

上述途徑雖然實現(xiàn)了多線程的安全訪問,但是在多線程高并發(fā)訪問的情況下,給此方法加上synchronized關(guān)鍵字會使得性能大不如前。我們仔細分析一下不難發(fā)現(xiàn),使用了synchronized關(guān)鍵字對整個getInstance()方法進行同步是沒有必要的:我們只要保證實例化這個對象的那段邏輯被一個線程執(zhí)行就可以了,而返回引用的那段代碼是沒有必要同步的。按照這個想法,我們的代碼片段大致如下所示:

 
 
  1. public class DoubleCheckSingleton {   
  2.     private volatile static DoubleCheckSingleton instatnce = null;   
  3.  
  4.     //constructors   
  5.  
  6.     public static DoubleCheckSingleton getInstance() {   
  7.         if (instatnce == null) {   //check if it is created.   
  8.             synchronized (DoubleCheckSingleton.class) {  //synchronize creation block   
  9.                 if (instatnce == null)   //double check if it is created   
  10.                     instatnce = new DoubleCheckSingleton();   
  11.             }   
  12.         }   
  13.         return instatnce;   
  14.     }   
  15. }   

代碼注解:

l 在getInstance()方法里,我們首先判斷此實例是否已經(jīng)被創(chuàng)建了,如果還沒有創(chuàng)建,首先使用synchronized同步實例化代碼塊。在同步代碼塊里,我們還需要再次檢查是否已經(jīng)創(chuàng)建了此類的實例,這是因為:如果沒有第二次檢查,這時有兩個線程Thread A和Thread B同時進入該方法,它們都檢測到instatnce為null,不管哪一個線程先占據(jù)同步鎖創(chuàng)建實例對象,都不會阻止另外一個線程繼續(xù)進入實例化代碼塊重新創(chuàng)建實例對象,這樣,同樣會生成兩個實例對象。所以,我們在同步的代碼塊里,進行第二次判斷判斷該對象是否已被創(chuàng)建。

正是由于使用了兩次的檢查,我們稱之為double-checked locking模式。

l 屬性instatnce是被volatile修飾的,因為volatile具有synchronized的可見性特點,也就是說線程能夠自動發(fā)現(xiàn)volatile變量的最新值。這樣,如果instatnce實例化成功,其他線程便能立即發(fā)現(xiàn)。

注意:

此程序只有在JAVA 5及以上版本才能正常運行,在以前版本不能保證其正常運行。這是由于Java平臺的內(nèi)存模式容許out-of-order writes引起的,假定有兩個線程,Thread 1和Thread 2,它們執(zhí)行以下步驟:

1. Thread 1發(fā)現(xiàn)instatnce沒有被實例化,它獲得鎖并去實例化此對象,JVM容許在沒有完全實例化完成時,instance變量就指向此實例,因為這些步驟可以是out-of-order writes的,此時instance==null為false,之前的版本即使用volatile關(guān)鍵字修飾也無效。

2. 在初始化完成之前,Thread 2進入此方法,發(fā)現(xiàn)instance已經(jīng)不為null了,Thread 2便認(rèn)為該實例初始化完成了,使用這個未完全初始化的實例對象,則很可能引起系統(tǒng)的崩潰。

3.3.4 Initialization on demand holder

要使用線程安全的延遲的單例初始化,我們還有一種方法,稱為Initialization on demand holder模式,代碼如下所示:

 
 
  1. public class LazyLoadedSingleton {   
  2.     private LazyLoadedSingleton() {   
  3.        }   
  4.  
  5.        private static class LazyHolder {  //holds the singleton class   
  6.               private static final LazyLoadedSingleton singletonInstatnce = new LazyLoadedSingleton();   
  7.        }   
  8.  
  9.        public static LazyLoadedSingleton getInstance() {   
  10.               return LazyHolder.singletonInstatnce;   
  11.        }   
  12. }   

當(dāng)JVM加載LazyLoadedSingleton類時,由于該類沒有static屬性,所以加載完成后便即可返回。只有第一次調(diào)用getInstance()方法時,JVM才會加載LazyHolder類,由于它包含一個static屬性singletonInstatnce,所以會首先初始化這個變量,根據(jù)前面的介紹,我們知道此過程并不會出現(xiàn)并發(fā)問題(JLS保證),這樣即實現(xiàn)了一個既線程安全又支持延遲加載的單例模式。

3.3.5 Singleton的序列化

如果單例類實現(xiàn)了Serializable接口,這時我們得特別注意,因為我們知道在默認(rèn)情況下,每次反序列化(Desierialization)總會創(chuàng)建一個新的實例對象,這樣一個系統(tǒng)會出現(xiàn)多個對象供使用。我們應(yīng)該怎么辦呢?

熟悉Java序列化的讀者可能知道,我們需要在readResolve()方法里做文章,此方法在反序列化完成之前被執(zhí)行,我們在此方法里替換掉反序列化出來的那個新的實例,讓其指向內(nèi)存中的那個單例對象即可,代碼實現(xiàn)如下:

 
 
  1. import java.io.Serializable;   
  2.  
  3. public class SerialibleSingleton implements Serializable {   
  4.     private static final long serialVersionUID = -6099617126325157499L;   
  5.     static SerialibleSingleton singleton = new SerialibleSingleton();   
  6.  
  7.     private SerialibleSingleton() {   
  8.     }   
  9.  
  10.     // This method is called immediately after an object of this class is deserialized.   
  11.     // This method returns the singleton instance.   
  12.     private Object readResolve() {   
  13.         return singleton;   
  14.     }   
  15. }   

方法readResolve()直接返回singleton單例,這樣,我們在內(nèi)存中始終保持了一個唯一的單例對象。

3.4 總結(jié)

通過這一章的學(xué)習(xí),我相信大家對于基本的單例模式已經(jīng)有了一個比較充分的認(rèn)識。其實我們這章討論的是在同一個JVM中,如何保證一個類只有一個單例,如果在分布式環(huán)境中,我們可能需要考慮如何保證在整個應(yīng)用(可能分布在不同JVM上)只有一個實例,但這也超出本書范疇,在這里將不再做深入研究,有興趣的讀者可以查閱相關(guān)資料深入研究。

________________________________________

[1] Static屬性和Static初始化塊(Static Initializers)的初始化過程是串行的,這個由JLS(Java Language Specification)保證,參見James Gosling, Bill Joy, Guy Steele and Gilad Bracha編寫的《 The Java? Language Specification Third Edition》一書的12.4一節(jié)。

原文鏈接:http://redhat.iteye.com/blog/1007884


當(dāng)前題目:漫談設(shè)計模式-技術(shù)要點詳解
當(dāng)前路徑:http://uogjgqi.cn/article/cdcoheh.html
掃二維碼與項目經(jīng)理溝通

我們在微信上24小時期待你的聲音

解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流