原網址出自:http://www.cnblogs.com/zhangliang88/p/5388472.html
單例模式:http://cantellow.iteye.com/blog/838473
單例多例需要搞明白兩個問題:
1. 什么是單例多例;
2. 如何產生單例多例;
3. 為什么要用單例多例
4. 什么時候用單例,什么時候用多例;
1. 什么是單例、多例:
所謂單例就是所有的請求都用一個對象來處理,比如我們常用的service和dao層的對象通常都是單例的,而多例則指每個請求用一個新的對象來處理,比如action;
一、單例模式和多例模式說明:
1.單例模式和多例模式屬於對象模式。
2.單例模式的對象在整個系統中只有一份,多例模式可以有多個實例。
3.它們都不對外提供構造方法,即構造方法都為私有。
二、應用舉例
1.單例模式舉例:
第一種:懶漢式(線程不安全,加上synchronized后線程安全)
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
第二種:餓漢式(線程安全)
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
這種方式基於classloder機制避免了多線程的同步問題,不過,instance在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用getInstance方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到lazy loading的效果。
public class Singleton { private Singleton instance = null; static { instance = new Singleton(); } private Singleton (){} public static Singleton getInstance() { return this.instance; } }
表面上看起來差別挺大,其實差不多,都是在類初始化即實例化instance。
第三種:靜態內部類
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
這種方式同樣利用了classloder的機制來保證初始化instance時只有一個線程,它跟餓漢式不同的是(很細微的差別):餓漢式是只要Singleton類被裝載了,那么instance就會被實例化(沒有達到lazy loading效果),而這種方式是Singleton類被裝載了,instance不一定被初始化。因為SingletonHolder類沒有被主動使用,只有顯示通過調用getInstance方法時,才會顯示裝載SingletonHolder類,從而實例化instance。想象一下,如果實例化instance很消耗資源,我想讓他延遲加載,另外一方面,我不希望在Singleton類加載時就實例化,因為我不能確保Singleton類還可能在其他的地方被主動使用從而被加載,那么這個時候實例化instance顯然是不合適的。這個時候,這種方式相比餓漢式就顯得很合理。
第四種:枚舉
public enum Singleton { INSTANCE; public void whateverMethod() { } }
這種方式是Java作者提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象,可謂是很堅強的壁壘啊,不過,個人認為由於1.5中才加入enum特性,用這種方式寫不免讓人感覺生疏,在實際工作中,我也很少看見有人這么寫過。
public enum EnumTest { MON, TUE, WED, THU, FRI, SAT, SUN; } public class Test{ public static void main(String[] args) { for (EnumTest e : EnumTest.values()) { System.out.println(e.toString()); } System.out.println("----------------我是分隔線------------------"); EnumTest test = EnumTest.MON; switch (test) { case MON: System.out.println("今天是星期一"); break; case TUE: System.out.println("今天是星期二"); break; // ... ... default: System.out.println(test); break; } } }
打印結果
MON
TUE
WED
THU
FRI
SAT
SUN
----------------我是分隔線------------------
今天是星期一
第五種:雙重校驗鎖
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
這個是第一種方式的升級版,俗稱雙重檢查鎖定。在JDK1.5之后,雙重檢查鎖定才能夠正常達到單例效果。
2. 多例模式舉例:
import java.text.*; import java.util.*; class NumberFormatTest { public static void displayNumber(Double d,Locale l) { NumberFormat nf; String dOut; nf = NumberFormat.getNumberInstance(l); dOut = nf.format(d); System.out.println(dOut + " " + l.toString()); } public static void main(String[] args) { displayNumber(1234567.89,new Locale("en","US")); displayNumber(1234567.89,new Locale("de","DE")); displayNumber(1234567.89,new Locale("fr","FR")); displayNumber(1234567.89,new Locale("zh","CN")); } }
一個根據語言代碼和地區代碼格式化數字的多例模式例子
2. 如何產生單例、多例:
在通用的SSH中,單例在spring中是默認的,如果要產生多例,則在配置文件的bean中添加scope="prototype";
我就告訴你昨天我得問題你估計就明白了
我沒用scope="prototype"就出現上面得驗證問題了 連續點提交就這樣
我添加后 不論怎么點都只會出現一個驗證提示
3. 為什么用單例、多例:
之所以用單例,是因為沒必要每個請求都新建一個對象,這樣子既浪費CPU又浪費內存;
之所以用多例,是為了防止並發問題;即一個請求改變了對象的狀態,此時對象又處理另一個請求,而之前請求對對象狀態的改變導致了對象對另一個請求做了錯誤的處理;
用單例和多例的標准只有一個:
當對象含有可改變的狀態時(更精確的說就是在實際應用中該狀態會改變),則多例,否則單例;
4. 何時用單例?何時用多例?
對於struts2來說,action必須用多例,因為action本身含有請求參數的值,即可改變的狀態;
而對於STRUTS1來說,action則可用單例,因為請求參數的值是放在actionForm中,而非action中的;
另外要說一下,並不是說service或dao一定是單例,標准同第3點所講的,就曾見過有的service中也包含了可改變的狀態,同時執行方法也依賴該狀態,但一樣用的單例,這樣就會出現隱藏的BUG,而並發的BUG通常很難重現和查找;