一、單例模式概述
單例模式定義很簡單:一個類中能創(chuàng)建一個實例,所以稱之為單例。
那我們?yōu)槭裁匆褂脝卫J侥兀?/p>
- 那既然一個類中只能創(chuàng)建一個實例,那么可以說這是跟類的狀態(tài)與對象無關(guān)的了。
- 頻繁創(chuàng)建對象、管理對象是一件耗費資源的事,我們只需要創(chuàng)建一個對象來用就足夠了。
如果你學(xué)過J2EE,你可能知道:
- Servlet是單例
- Struts2是多例
- SpringMVC是單例的
Struts2為啥設(shè)計成多例呢?
- 這主要是由于設(shè)計層面上的問題,Struts2是基于Filter攔截類的,ognl引擎對變量是注入的,所以它要設(shè)計成多例
二、單例模式示例
編寫單例模式的代碼其實很簡單,分為三步:
- 將構(gòu)造函數(shù)私有化
- 在類的內(nèi)部創(chuàng)建示例
- 提供獲取唯一實例的方法
2.1 餓漢式
根據(jù)上面的步驟,我們就可以創(chuàng)建單例模式了。
public class Java2y {
// 1.將構(gòu)造函數(shù)私有化,不可以通過new的方式來創(chuàng)建對象
private Java2y(){}
// 2.在類的內(nèi)部創(chuàng)建實例
private static Java2y y = new Java2y();
// 3.提供獲取唯一實例的方法
public static Java2y getInstance(){
return y;
}
}
這種代碼我們稱之為“餓漢式”:
- 一上來就創(chuàng)建了對象,如果該實例從始至終都沒被使用過,則會造成內(nèi)存浪費。
2.2 簡單餓漢式
既然一上來就創(chuàng)建對象會造成內(nèi)存浪費,那我們設(shè)計成用到的時候再創(chuàng)建對象
public class Java2y {
// 1.將構(gòu)造函數(shù)私有化,不可以通過new的方式來創(chuàng)建對象
private Java2y(){}
// 2.1先不創(chuàng)建對象,等用到的時候再創(chuàng)建
private static Java2y y = null;
// 2.2調(diào)用這個方法,創(chuàng)建對象
public static Java2y getInstance(){
// 3.如果對象為null,就創(chuàng)建并返回
if(y == null){
y = new Java2y();
}
return y;
}
}
上面的代碼不行嗎?在單線程環(huán)境下是可行的,如果在多線程環(huán)境下就有問題了,解決方法也很簡單,加鎖就行。
public class Java2y {
// 1.將構(gòu)造函數(shù)私有化,不可以通過new的方式來創(chuàng)建對象
private Java2y(){}
// 2.1先不創(chuàng)建對象,等用到的時候再創(chuàng)建
private static Java2y y = null;
// 2.2調(diào)用這個方法,創(chuàng)建對象
public static synchronized Java2y getInstance(){
// 3.如果對象為null,就創(chuàng)建并返回
if(y == null){
y = new Java2y();
}
return y;
}
}
2.3 雙重檢測機制(DCL)懶漢式
上面那種直接在方法上加鎖的方式其實不夠好,因為在方法上加了內(nèi)置鎖,在多線程環(huán)境下性能會比較低,所以我們可以將鎖的范圍縮小。
public class Java2y {
private Java2y(){}
private static Java2y y = null;
private static Java2y getInstance(){
if(y == null){
// 將鎖的范圍縮小,提高性能
synchronized(Java2y.class){
y = new Java2y();
}
}
return y;
}
}
這樣寫以后可行了嗎?不行,因為雖然加了鎖,但還是有可能創(chuàng)建出兩個對象出來:
- 線程1和線程2同時調(diào)用getInstance()方法,它們同時判斷y==null,因為結(jié)果都為null,所以進入了if代碼塊。
- 此時線程1得到CPU的控制權(quán)-->進入同步代碼塊-->創(chuàng)建對象-->返回對象
- 線程1完成后,線程2得到了CPU控制權(quán),一樣是進入同步代碼塊-->創(chuàng)建對象-->返回對象
- 然后很明顯,最終返回了不止一個對象
然后有人又想到了:進入同步代碼塊時再判斷一下對象是否存在就行了吧,于是有了下面的代碼:
public class Java2y {
private Java2y(){}
private static Java2y y = null;
private static Java2y getInstance(){
if(y == null){
// 將鎖的范圍縮小,提高性能
synchronized(Java2y.class){
if(y == null){
y = new Java2y();
}
}
}
return y;
}
}
然后這種方式又出現(xiàn)了重排序的問題?。?!怎么解決呢?加上volatile關(guān)鍵字吧,volatile有內(nèi)存屏障的功能!
所以說完整的DCL代碼是這樣子的:
public class Java2y {
private Java2y(){}
private static volatile Java2y y = null;
private static Java2y getInstance(){
// 這個判空是為了提高性能
if(y == null){
// 將鎖的范圍縮小,提高性能
synchronized(Java2y.class){
if(y == null){
y = new Java2y();
}
}
}
return y;
}
}
2.4 靜態(tài)內(nèi)部類懶漢式
它的原理是這樣的:
- 當任何一個線程第一次調(diào)用getInstance()時,都會使SingletonHolder被加載和被初始化,此時靜態(tài)初始化器將執(zhí)行Singleton的初始化操作。(被調(diào)用時才進行初始化?。?/li>
- 初始化靜態(tài)數(shù)據(jù)時,Java提供了的線程安全性保證。(所以不需要任何的同步)
public class Java2y {
private Java2y (){}
// 使用私有靜態(tài)內(nèi)部類實現(xiàn)懶加載
private static class LazyHolder {
private static final Java2y INSTANCE = new Java2y();
}
public static Java2y getInstance(){
return LazyHolder.INSTANCE;
}
}
這種方式非常推薦使用?。?!
2.5 枚舉方式
public enum Java2y {
JAVA_2_y,
}
這種實現(xiàn):
- 簡單
- 防止多次實例化,即使是在復(fù)雜序列化或者反射攻擊時也很安全