當前位置:才華齋>計算機>java語言>

JAVA多執行緒併發下的單例模式應用

java語言 閱讀(2.18W)

單例模式應該是設計模式中比較簡單的一個,也是非常常見的,但是在多執行緒併發的環境下使用卻是不那麼簡單了,今天小編給大家分享一個在開發過程中遇到的單例模式的應用。

JAVA多執行緒併發下的單例模式應用

單例模式應該是設計模式中比較簡單的一個,也是非常常見的,但是在多執行緒併發的環境下使用卻是不那麼簡單了,今天給大家分享一個我在開發過程中遇到的單例模式的應用。

  首先我們先來看一下單例模式的定義:

一個類有且僅有一個例項,並且自行例項化向整個系統提供。

  單例模式的要素:

1.私有的靜態的.例項物件

2.私有的建構函式(保證在該類外部,無法通過new的方式來建立物件例項)

3.公有的、靜態的、訪問該例項物件的方法

單例模式分為懶漢形和餓漢式

懶漢式:

應用剛啟動的時候,並不建立例項,當外部呼叫該類的例項或者該類例項方法的時候,才建立該類的例項。(時間換空間)

優點:例項在被使用的時候才被建立,可以節省系統資源,體現了延遲載入的思想。

缺點:由於系統剛啟動時且未被外部呼叫時,例項沒有建立;如果一時間有多個執行緒同時呼叫azyInstance()方法很有可能會產生多個例項。

例子:

publicclassSingletonClass{

//私有建構函式,保證類不能通過new建立

privateSingletonClass(){}

privatestaticSingletonClassinstance=null;

publicstaticSingletonClassgetInstance(){

if(instance==null){

//建立本類物件

instance=newSingletonClass();

}

returninstance;

}

}

餓漢式:

應用剛啟動的時候,不管外部有沒有呼叫該類的例項方法,該類的例項就已經建立好了。(空間換時間。)

優點:寫法簡單,在多執行緒下也能保證單例例項的唯一性,不用同步,執行效率高。

缺點:在外部沒有使用到該類的時候,該類的例項就建立了,若該類例項的建立比較消耗系統資源,並且外部一直沒有呼叫該例項,那麼這部分的系統資源的消耗是沒有意義的。

例子:

publicclassSingleton{

//首先自己在內部定義自己的一個例項,只供內部呼叫

privatestaticfinalSingletoninstance=newSingleton();

//私有建構函式

privateSingleton(){

}

//提供了靜態方法,外部可以直接呼叫

publicstaticSingletongetInstance(){

returninstance;

}

}

下面模擬單例模式在多執行緒下會出現的問題

/**

*懶漢式單例類

*/

publicclassLazySingleton{

//為了易於模擬多執行緒下,懶漢式出現的問題,我們在建立例項的建構函式裡面使當前執行緒暫停了50毫秒

privateLazySingleton(){

try{

p(50);

}catch(InterruptedExceptione){

tStackTrace();

}

tln("生成LazySingleton例項一次!");

}

privatestaticLazySingletonlazyInstance=null;

publicstaticLazySingletongetLazyInstance(){

if(lazyInstance==null){

lazyInstance=newLazySingleton();

}

returnlazyInstance;

}

}

測試程式碼:我們在測試程式碼裡面新建了10個執行緒,讓這10個執行緒同時呼叫azyInstance()方法

publicclassSingletonTest{

publicstaticvoidmain(String[]args){

//建立十個執行緒調

for(inti=0;i<10;i++){

newThread(){

@Override

publicvoidrun(){

azyInstance();

}

}t();

}

}

}

結果:

生成LazySingleton例項一次!

生成LazySingleton例項一次!

生成LazySingleton例項一次!

生成LazySingleton例項一次!

生成LazySingleton例項一次!

生成LazySingleton例項一次!

生成LazySingleton例項一次!

生成LazySingleton例項一次!

生成LazySingleton例項一次!

生成LazySingleton例項一次!

可以看出單例模式懶漢式在多執行緒的併發下也會出現問題,

分析一下:多個執行緒同時訪問上面的懶漢式單例,現在有兩個執行緒A和B同時訪問azyInstance()方法。

假設A先得到CPU的時間切片,A執行到if(lazyInstance==null)時,由於lazyInstance之前並沒有例項化,所以lazyInstance==null為true,在還沒有執行例項建立的時候

此時CPU將執行時間分給了執行緒B,執行緒B執行到if(lazyInstance==null)時,由於lazyInstance之前並沒有例項化,所以lazyInstance==null為true,執行緒B繼續往下執行例項的建立過程,執行緒B建立完例項之後,返回。

此時CPU將時間切片分給執行緒A,執行緒A接著開始執行例項的建立,例項建立完之後便返回。由此看執行緒A和執行緒B分別建立了一個例項(存在2個例項了),這就導致了單例的失效。

解決辦法:我們可以在getLazyInstance方法上加上synchronized使其同步,但是這樣一來,會降低整個訪問的速度,而且每次都要判斷。

那麼有沒有更好的方式來實現呢?我們可以考慮使用"雙重檢查加鎖"的方式來實現,就可以既實現執行緒安全,又能夠使效能不受到很大的影響。我們看看具體解決程式碼

publicclassLazySingleton{

privateLazySingleton(){

try{

p(50);

}catch(InterruptedExceptione){

tStackTrace();

}

tln("生成LazySingleton例項一次!");

}

privatestaticLazySingletonlazyInstance=null;

publicstaticLazySingletongetLazyInstance(){

//先檢查例項是否存在,如果不存在才進入下面的同步塊

if(lazyInstance==null){

//同步塊,執行緒安全地建立例項

synchronized(s){

//再次檢查例項是否存在,如果不存在才真正地建立例項

if(lazyInstance==null){

lazyInstance=newLazySingleton();

}

}

}

returnlazyInstance;

}

}

這樣我們就可以在多執行緒併發下安全應用單例模式中的懶漢模式。這種方法在程式碼上可能就不怎麼美觀,我們可以優雅的使用一個內部類來維護單例類的例項,下面看看程式碼

publicclassGracefulSingleton{

privateGracefulSingleton(){

tln("建立GracefulSingleton例項一次!");

}

//類級的內部類,也就是靜態的成員式內部類,該內部類的例項與外部類的例項沒有繫結關係,而且只有被呼叫到才會裝載,從而實現了延遲載入

privatestaticclassSingletonHoder{

//靜態初始化器,由JVM來保證執行緒安全

privatestaticGracefulSingletoninstance=newGracefulSingleton();

}

publicstaticGracefulSingletongetInstance(){

ance;

}

}

說一下我在實際開發中的場景:為了程式的高效率使用多執行緒併發,然而是迴圈呼叫,可能導致建立執行緒數過多,考慮採用執行緒池管理,這時候建立執行緒池仍然是處於迴圈呼叫中,也可能導致多個執行緒池,這時候就考慮使用單例模式。

原始碼:

publicclassThreadPoolFactoryUtil{

privateExecutorServiceexecutorService;

//在建構函式中建立執行緒池

privateThreadPoolFactoryUtil(){

//獲取系統處理器個數,作為執行緒池數量

intnThreads=untime()lableProcessors();

executorService=ixedThreadPool(nThreads);

}

//定義一個靜態內部類,內部定義靜態成員建立外部類例項

privatestaticclassSingletonContainer{

privatestaticThreadPoolFactoryUtilutil=newThreadPoolFactoryUtil();

}

//獲取本類物件

publicstaticThreadPoolFactoryUtilgetUtil(){

;

}

publicExecutorServicegetExecutorService(){

returnexecutorService;

}

}

涉及到一個靜態內部類,我們看看靜態內部類的特點:

1、靜態內部類無需依賴於外部類,它可以獨立於外部物件而存在。

2、靜態內部類,多個外部類的物件可以共享同一個內部類的物件。

3、使用靜態內部類的好處是加強了程式碼的封裝性以及提高了程式碼的可讀性。

4、普通內部類不能宣告static的方法和變數,注意這裡說的是變數,常量(也就是finalstatic修飾的屬性)還是可以的,而靜態內部類形似外部類,沒有任何限制。可以直接被用外部類名+內部類名獲得。

以上是我在實際開發中遇到的一些問題,部分摘自網上程式碼,結合實開發際案例。如有不妥,希望大家及時指出!