項目中需要有一些工具類提供服務(wù),例如線程池陆馁,http調(diào)用工具等岖免,有兩種設(shè)計:單例和靜態(tài)方法方式岳颇。
單例方式:如果這個類需要維持一些狀態(tài),比較適合使用單例模式存在颅湘。單例模式還可以延遲加載赦役,而且單例更加面向?qū)ο螅梢岳^承基類實現(xiàn)接口等方式提供不同功能的實現(xiàn)栅炒。
靜態(tài)方法:如果一些不需要維護(hù)狀態(tài)的類可以設(shè)計成靜態(tài)方法掂摔,訪問效率會好一些。例如java中的Math類赢赊。
單例大概有幾種實現(xiàn)方式乙漓,餓漢式、懶漢式释移、雙重檢驗鎖叭披、靜態(tài)內(nèi)部類等。
餓漢式線程安全玩讳,類加載時即加載涩蜘,不支持延時加載,而懶漢式延遲加載熏纯,但不是線程安全的同诫。
1、雙重檢驗鎖單例:
public class SingleTon {
private volatile static SingleTon singleTon = null;
private SingleTon(){}
public static SingleTon getSingleTon() {
if (singleTon == null) {
synchronized (SingleTon.class) {
if (singleTon == null) {
singleTon = new SingleTon();
}
}
}
return singleTon;
}
}
第一個null判斷是為了減少進(jìn)入synchronized塊解決效率問題樟澜。
第二個null判斷是為了攔截:線程a進(jìn)入synchronized塊后實例化釋放鎖后误窖,線程b從阻塞中醒來再次進(jìn)入synchronized塊執(zhí)行加載。
jvm有指令重排秩贰,所以需要使用volatile關(guān)鍵字防止指令重排霹俺。
singleTon = new SingleTon();不是線程安全的操作,包含了3個步驟毒费。
1.在堆內(nèi)為SingleTon開辟空間丙唧。
2.在堆實例化SingleTon對象,加載各個參數(shù)觅玻。
3.將singleTon引用指向這塊內(nèi)存想际。
如果不使用volatile關(guān)鍵字培漏,jvm可能發(fā)生這樣的執(zhí)行序列:1-3-2,在沒有實例化完成時就將引用指向了這塊內(nèi)存沼琉,導(dǎo)致代碼判斷null為非空,將對象返回使用桩匪,造成問題打瘪。
2、靜態(tài)內(nèi)部類單例
public class AsyncExecutorUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(AsyncExecutorUtils.class);
private AsyncExecutorUtils() {
LOGGER.info("AsyncExecutorUtils lazy init by jvm...");
}
private static class ExecutorHolder {
static AsyncExecutorUtils INSTANCE = new AsyncExecutorUtils();
}
public static AsyncExecutorUtils getInstance() {
return ExecutorHolder.INSTANCE;
}
這是我的一段代碼傻昙,線程池采用了單例模式闺骚。將獲取實例的靜態(tài)方法改成從靜態(tài)內(nèi)部類ExecutorHolder里面的靜態(tài)成員變量INSTANCE里面獲取。
靜態(tài)內(nèi)部類方式如何實現(xiàn)延遲加載:
因為我們將實例放在了內(nèi)部類里面妆档,所以要保證內(nèi)部類是延遲加載的僻爽,java中的類分為主動引用與被動引用的區(qū)別,只有主動引用的時候才會初始化該類贾惦。主動引用有且只有5中情況:
1.new胸梆、getstatic、putstatic或invokestatic這四條字節(jié)碼指令须板,對應(yīng)常用的操作是使用new關(guān)鍵字實例化對象碰镜、獲取或者設(shè)置靜態(tài)變量,調(diào)用靜態(tài)方法习瑰。(newarray指令觸發(fā)的只是數(shù)組類型本身的初始化绪颖,而不會導(dǎo)致其相關(guān)類型的初始化)
2.反射調(diào)用。
3.如果是某一個類的父類甜奄,這個子類初始化會觸發(fā)父類初始化柠横。
4.main方法所在類
5.當(dāng)使用JDK 1.7等動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getStatic课兄、REF_putStatic牍氛、REF_invokeStatic的方法句柄,并且這個方法句柄所對應(yīng)的類沒有進(jìn)行過初始化烟阐,則需要先觸發(fā)其初始化糜俗。
其他方式均為被動引用,不會被初始化曲饱。所以這個靜態(tài)內(nèi)部類ExecutorHolder只有在程序中第一次使用時調(diào)用到他的靜態(tài)獲取方法getInstance()時才會初始化悠抹,實現(xiàn)延時加載。
靜態(tài)內(nèi)部類方式如何保證線程安全:
可以看到getInstance()只返回同一個static的INSTANCE扩淀,而類的初始化是由jvm初始化楔敌,jvm肯定會保證類的初始化的線程安全性。具體實現(xiàn)大致是保證只有一個線程獲得鎖執(zhí)行初始化驻谆,其他阻塞線程恢復(fù)過來不會再去執(zhí)行初始化卵凑。
感覺上靜態(tài)內(nèi)部類實現(xiàn)更加優(yōu)雅簡潔庆聘,但是這種方式不能傳參數(shù),但是大多數(shù)這種單例類都不需要傳參勺卢。