1.單例模式概念
單例模式(Singleton Pattern)負責創建自己的對象,同時確保程序中該類只有一個對象被創建。
2.單例模式作用
確保程序中一個類最多只有一個實例,提供訪問這個類的全局點。
3.優點和缺點
優點
1)在內存里只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷毀實例(比如管理學院首頁頁面緩存)。
2)避免對資源的多重占用(比如寫文件操作)。
缺點
不能繼承,與單一職責原則沖突,一個類應該只關心內部邏輯,而不關心外面怎么樣來實例化。
4.注意事項
1)在JAVA中實現單例模式,需要一個私有的構造器,一個靜態方法和一個靜態變量。
2)雙重檢查加鎖不適用于JDK1.4及以前的版本。
3)如果使用多個類加載器,可能導致單例失效,而產生多個實例。
4)如果使用JVM1.2或之前的版本,你必須建立單例注冊表,以免垃圾收集器將單例回收。
5.例子解析
線程不安全,懶漢模式
/**
* @author Administrator
* 線程不安全
*/
public class Singleton {
//利用static來記錄Singleton唯一的實例
private static Singleton singleton = null;
//將構造方法聲明為私有,只有Singleton類內部才能調用構造器
private Singleton() {
}
//用getInstance方法實例化對象,并返回這個實例
public static Singleton getInstance()
{
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
線程安全,餓漢模式
這里的懶漢和餓漢主要指的是singleton 是在什么時候實例化
//這種在聲明變量的時候就實例化的就是餓漢模式
private static Singleton singleton = new Singleton();
//這種要先判斷singleton == null,就是懶漢模式
public static Singleton getInstance()
{
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
/**
* @author Administrator
* 使用synchronized實現了線程安全,但是效率不高,會造成程序執行效率降低
*/
public class Singleton {
//利用static來記錄Singleton唯一的實例
private static Singleton singleton = new Singleton();
//將構造方法聲明為私有,只有Singleton類內部才能調用構造器
private Singleton() {
}
//用getInstance方法實例化對象,并返回這個實例
//關鍵字synchronized實現線程安全
public static synchronized Singleton getInstance()
{
return singleton;
}
}
雙重檢查加鎖
/**
* @author Administrator
* 雙重檢查加鎖
* JDK大于1.4的版本,雙重檢查加鎖才能生效
*/
public class Singleton {
//利用static來記錄Singleton唯一的實例
//volatile確保singleton變量被初始化成為Singleton實例后,多個線程能夠正確的處理singleton變量
private volatile static Singleton singleton = null;
//將構造方法聲明為私有,只有Singleton類內部才能調用構造器
private Singleton() {
}
//用getInstance方法實例化對象,并返回這個實例
public static Singleton getInstance()
{
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
6.總結
1)volatile的作用,關于這個網上有很多資料,每個我都會先看了評論,然后覺得有一篇文章(文章鏈接會在參考文章里附上)講得比較仔細,而且應該也沒有什么大問題。它總結了volatile的作用:
一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之后,那么就具備了兩層語義:
<1>保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
<2>禁止進行指令重排序。
它里面有個例子,闡述了volatile的使用場景。首先,需要知道的是當程序在運行過程中,會將運算需要的數據從主存復制一份到CPU的高速緩存當中,那么CPU進行計算時就可以直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束之后,再將高速緩存中的數據刷新到主存當中。
舉個例子,
int i = i+1;
當線程執行這個語句時,會先從主存當中讀取i的值,然后復制一份到高速緩存當中,然后CPU執行指令對i進行加1操作,然后將數據寫入高速緩存,最后將高速緩存中i最新的值刷新到主存當中。
當在多線程的情況下,假設有兩個線程,i初始值是0。初始時,兩個線程分別讀取i的值存入各自所在的CPU的高速緩存當中,然后線程一進行加1操作,然后把i的最新值1寫入到內存。此時線程二的高速緩存當中i的值還是0,進行加1操作之后,i的值為1,然后線程二把i的值寫入內存,最終i的值是等于1,而不是2。這不是我們想要的結果。
那么這時候就可以用到volatile了。
當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值。
而普通的共享變量不能保證可見性,因為普通共享變量被修改之后,什么時候被寫入主存是不確定的,當其他線程去讀取時,此時內存中可能還是原來的舊值,因此無法保證可見性。
但是,我們還是需要明白,volatile只是保證兩個線程都能從主內存中讀取到最新的變量值,但是卻不能保證線程安全。
通過上面的例子說一種可能發生的情況,假設當前的i在主內存中的值是3,當線程一和線程二同時讀取主內存中的i的值進行操作的時候,它們拿到的都是i=3,然后進行各自的操作,加1后,兩個線程得到的結果都是4,線程一會將結果4寫回到主內存中,而線程二也會將結果4寫到主內存中,這樣就導致i的結果是4,而不是5。
2)JAVA主內存和工作內存
在查找關于volatile的資料的時候,涉及到了主內存和工作內存的概念,所以也在網上查找了一下。
主內存主要對應于java堆中對象的實例數據部分,而工作內存則對應于虛擬機棧中的部分區域。
從更底層來說,主內存就是硬件的內存,而為了獲取更好的運行速度,虛擬機及硬件系統可能會讓工作內存優先存儲于寄存器和高速緩存。
這個概念沒有認真驗證是對得還是錯的,可以搜索JAVA內存模型可以了解得更具體一點。
3)類加載器
在注意事項里提到了如果使用多個類加載器,可能導致單例失效,而產生多個實例。可以搜索JAVA自定義類加載器和查看
http://www.codeweblog.com/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E4%B8%8E%E5%8D%95%E4%BE%8B/
7.源碼地址
http://download.csdn.net/detail/lgywsdy/9748795
8.參考文章
http://blog.csdn.net/sunxianghuang/article/details/51920794
http://www.cnblogs.com/dolphin0520/p/3920373.html
http://www.runoob.com/design-pattern/singleton-pattern.html