1. 程式人生 > >java 單例模式及在SSH框架中運用

java 單例模式及在SSH框架中運用

定義:

確保某一個類只有一個例項,而且自動例項化並向整個系統提供這個例項。

程式碼:

Singleton類稱為單例類,通過使用private的建構函式確保了在一個應用中只產生一個例項,並且是自行例項化的。

Java程式碼  收藏程式碼
  1. /** 
  2.  * 執行緒安全的單例模式 
  3.  * 餓漢式單例 
  4.  * @author Administrator 
  5.  * 
  6.  */  
  7. public class Singleton {  
  8.     private static final Singleton singleton = new Singleton();  
  9.     // 限制產生多個物件  
  10.     private
     Singleton() {  
  11.     }  
  12.     // 通過該方法獲得例項物件  
  13.     public static Singleton getSingleton() {  
  14.         return singleton;  
  15.     }  
  16.     // 類中其他方法儘量是static  
  17.     public static void doSomething() {  
  18.     }  
  19. }  

單例模式的優點:

  1. 由於單例模式在記憶體中只有一個例項,減少了記憶體開支,特別是一個物件需要頻繁地建立、銷燬時,而且建立或銷燬時效能又無法優化,單例模式的優勢就非常明顯了。
  2. 由於單例模式只生成一個例項,所以減少了系統的效能開銷,當一個物件的產生需要比較多的資源時,如讀取配置、產生其他依賴物件時,則可以通過在應用啟動時直接產生一個單例物件,然後用永久駐留記憶體的方式來解決。
  3. 單例模式可以避免對資源的多重佔用,例如一個寫檔案動作,由於只有一個例項存在記憶體中,避免對同一個資原始檔的同時寫操作。
  4. 單例模式可以在系統設定全域性的訪問點,優化和共享資源訪問,例如可以設計一個單例類,負責所有資料表的對映處理。

單例模式的缺點:

  1. 單例模式一般沒有介面,擴充套件很困難,若要擴充套件,除了修改程式碼基本上沒有第二種途徑可以實現。
  2. 單例模式對測試是不利的。在並行開發環境中,如果單例模式沒有完成,是不能進行測試的,沒有介面也不能用mock的方式虛擬一個物件。
  3. 單例模式與單一職責原則有衝突。一個類應該只實現一個邏輯,而不關心它是否是單例的,是不是要單例取決於環境,單例模式把“要單例”和業務邏輯融合在一個類中。

單例模式的使用場景:

  1. 要求生成唯一序列號的環境;
  2. 在整個專案中需要一個共享訪問點或共享資料,例如一個Web頁面上的計數器,可以不用把每次重新整理都記錄到資料庫中,使用單例模式保持計數器的值,並確保是執行緒安全的;
  3. 建立一個物件需要消耗的資源過多,如要訪問IO和資料庫等資源;
  4. 需要定義大量的靜態常量和靜態方法(如工具類)的環境,可以採用單例模式(當然,也可以直接宣告為static的方式)。

單例模式的注意事項:

  1.  在高併發情況下,請注意單例模式的執行緒同步問題。

Java程式碼  收藏程式碼
  1. /** 
  2.  * 懶漢式單例 
  3.  * @author Administrator 
  4.  * 
  5.  */  
  6. public class Singleton2 {  
  7.     private static Singleton2 singleton = null;  
  8.     // 限制產生多個物件  
  9.     private Singleton2() {  
  10.     }  
  11.     // 通過該方法獲得例項物件  
  12.     public static Singleton2 getSingleton() {  
  13.         synchronized (singleton) {  
  14.             if (singleton == null) {  
  15.                 singleton = new Singleton2();  
  16.             }  
  17.         }  
  18.         return singleton;  
  19.     }  
  20. }  

如果不加synchronized進行控制,如果第一個執行緒A執行到singleton = new Singleton2(),但還沒有獲得物件,第二個執行緒B也在執行,執行到if (singleton == null)判斷,那麼執行緒B獲得判斷條件為真,於是繼續執行下去,執行緒A和B都獲得了物件,記憶體中就出現了兩個物件。

建議使用餓漢式單例,那是執行緒安全的單例模式。

  2.  需要考慮物件的複製情況。在Java中,若實現Cloneable介面,並實現了clone方法,則可以直接通過物件複製方式建立一個新物件,物件複製是不用呼叫類的建構函式的。

  3.  注意JVM的垃圾回收機制,如果我們的一個單例物件在記憶體中長久不使用,JVM就認為這是一個垃圾物件,在CPU資源空閒的情況下該物件會被清理掉,下次再呼叫時就需要產生一個新物件。如果該物件作為有狀態值的管理,則會出現狀態恢復原狀的情況,就會出現故障。

有兩種方法可以解決該問題 

1、由容器管理單例的生命週期 

Java EE容器或者框架級容器(如Spring)可以讓物件長久駐留記憶體。 

2、狀態隨時記錄 

可以使用非同步記錄的方式,或者使用觀察者模式,記錄狀態的變化,寫入檔案或寫入資料庫中,確保即使單例物件重新初始化也可以從資源環境獲得銷燬前的資料,避免應用資料丟失。

單例模式的擴充套件:

能產生固定數量例項的單例模式

Java程式碼  收藏程式碼
  1. /** 
  2.  * 能產生固定數量例項的單例模式 
  3.  * @author Administrator 
  4.  * 
  5.  */  
  6. public class Singleton3 {  
  7.     // 最多能產生的例項數  
  8.     private static int maxNumOfSingleton = 2;  
  9.     // 定義一個列表,容納所有例項  
  10.     private static ArrayList<Singleton3> singletonList = new ArrayList<Singleton3>();  
  11.     // 產生所有物件  
  12.     static {  
  13.         for (int i = 0; i < maxNumOfSingleton; i++) {  
  14.             singletonList.add(new Singleton3());  
  15.         }  
  16.     }  
  17.     // 限制其他類生成物件  
  18.     private Singleton3() {  
  19.     }  
  20.     // 隨機獲得一個例項  
  21.     public static Singleton3 getInstance() {  
  22.         Random random = new Random();  
  23.         return singletonList.get(random.nextInt(maxNumOfSingleton));  
  24.     }  
  25. }  

 在SSH中的運用

1.hibernate 中

我們知道Session是由SessionFactory負責建立的,而SessionFactory的實現是執行緒安全的,採用前面提到的“餓漢模式”建立單例。多個併發的執行緒可以同時訪問一個SessionFactory並從中獲取Session例項,那麼Session是否是執行緒安全的呢?很遺憾,答案是否定的。Session中包含了資料庫操作相關的狀態資訊,那麼說如果多個執行緒同時使用一個Session例項進行CRUD,就很有可能導致資料存取的混亂,你能夠想像那些你根本不能預測執行順序的執行緒對你的一條記錄進行操作的情形嗎?以上程式碼使用ThreadLocal模式的解決了這一問題。 只要藉助上面的工具類獲取Session例項,我們就可以實現執行緒範圍內的Session共享,從而避免了執行緒中頻繁的建立和銷燬Session例項。當然,不要忘記在用完後關閉Session。 
ThreadLocal和執行緒同步機制相比有什麼優勢呢? 
ThreadLocal和執行緒同步機制都是為了解決多執行緒中相同變數的訪問衝突問題。在同步機制中,通過物件的鎖機制保證同一時間只有一個執行緒訪問變數。這時該變數是多個執行緒共享的,使用同步機制要求程式慎密地分析什麼時候對變數進行讀寫,什麼時候需要鎖定某個物件,什麼時候釋放物件鎖等繁雜的問題,程式設計和編寫難度相對較大。而ThreadLocal則從另一個角度來解決多執行緒的併發訪問。ThreadLocal會為每一個執行緒提供一個獨立的變數副本,從而隔離了多個執行緒對資料的訪問衝突。因為每一個執行緒都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。ThreadLocal提供了執行緒安全的共享物件,在編寫多執行緒程式碼時,可以把不安全的變數封裝進ThreadLocal。 

由於ThreadLocal中可以持有任何型別的物件,低版本JDK所提供的get()返回的是Object物件,需要強制型別轉換。但JDK 5.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用。 
概括起來說,對於多執行緒資源共享的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變數,讓不同的執行緒排隊訪問,而後者為每一個執行緒都提供了一份變數,因此可以同時訪問而互不影響。 
ThreadLocal是解決執行緒安全問題一個很好的思路,它通過為每個執行緒提供一個獨立的變數副本解決了變數併發訪問的衝突問題。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決執行緒安全問題更簡單、更方便,且結果程式擁有更高的併發性。ThreadLocal在Spring中發揮著重要的作用,在管理request作用域的Bean、事務管理、任務排程、AOP等模組都出現了它們的身影,起著舉足輕重的作用。 
不過在使用執行緒池的情況下,使用ThreadLocal應該慎重,因為執行緒池中的執行緒是可重用的。 
ThreadLocal的內容以後還要仔細學習一下。

 2.spring中

專案載入的時候bean(一個bean對應某個類)自動建立(初始化,建一個例項),而後是每次呼叫bean的時候是注入的(不是重新new。。所有整個系統都是這個例項,,而且是spring自動提供的) 


一:對於Spring中實現Singleton模式,是以IOC容器為單位(就是說在這個容器裡面bean實現Singleton),換句話說,一個JVM可能有多個IOC容器,而在這個容器裡實現了singleton bean, 
而Java中實現Singleton模式而言,只有一個JVM,jvm中某個class只有一個例項 
二: 
singleton模式中,singleton的class在整個JVM中只有一個instance, 
Spring的Bean,你可以一個class配置多個Bean,這個class就有了多個instance。 

這個singleton是指在spring容器中,這個Bean是單例項的,是執行緒共享的。所以要求這些類都是執行緒安全的。也就是說,不能出現修改Bean屬性的方法,當然除了設值得那些setter。只要滿足執行緒安全,這些bean都可以用singleton。而且我們在絕大多數使用上,也是這樣用的,包括dao,service。 
Beanfactory是Spring初始以靜態方式載入的,Spring的單例IOC是基於容器級的,所以這你都不用擔心與考慮.

 3.struts

" Struts1 Action是單例模式並且必須是執行緒安全的,因為僅有Action的一個例項來處理所有的請求。單例策略限制了Struts1 Action能作的事,並且要在開發時特別小心。Action資源必須是執行緒安全的或同步的。 
" Struts2 Action物件為每一個請求產生一個例項,因此沒有執行緒安全問題。(實際上,servlet容器給每個請求產生許多可丟棄的物件,並且不會導致效能和垃圾回收問題) 

Action執行的控制: 
" Struts1支援每一個模組有單獨的Request Processors(生命週期),但是模組中的所有Action必須共享相同的生命週期。 
" Struts2支援通過攔截器堆疊(Interceptor Stacks)為每一個Action建立不同的生命週期。堆疊能夠根據需要和不同的Action一起使用。