單例模式及其應用

2021-09-24 10:58:43 字數 2729 閱讀 2483

所謂單例,指的就是單例項,有且僅有乙個類例項,這個單例不應該由人來控制,而應該由**來限制,強制單例。

單例有其獨有的使用場景,一般是對於那些業務邏輯上限定不能多例只能單例的情況,例如:類似於計數器之類的存在,一般都需要使用乙個例項來進行記錄,若多例計數則會不準確。

其實單例就是那些很明顯的使用場合,沒有之前學習的那些模式所使用的複雜場景,只要你需要使用單例,那你就使用單例,簡單易理解。

所以我認為有關單例模式的重點不在於場景,而在於如何使用。

1、常見的單例模式有兩種建立方式:所謂餓懶漢式與餓漢式

(1)懶漢式

何為懶?顧名思義,就是不做事,這裡也是同義,懶漢式就是不在系統載入時就建立類的單例,而是在第一次使用例項的時候再建立。

詳見下方**示例:

1 public class lhandanli 

6 //定義乙個公共的公開的方法來返回該類的例項,由於是懶漢式,需要在第一次使用時生成例項,所以為了執行緒安全,使用synchronized關鍵字來確保只會生成單例

7 public static synchronized lhandanli getinstance()

11 return dl;

12 }

13 }

(2)餓漢式

又何為餓?餓者,飢不擇食;但凡有食,必急食之。此處同義:在載入類的時候就會建立類的單例,並儲存在類中。

詳見下方**示例:

1 public class ehandanli 

6 //定義公開方法,返回已建立的單例

7 public static ehandanli getinstance()

10 }

2、雙重加鎖機制

何為雙重加鎖機制?

在懶漢式實現單例模式的**中,有使用synchronized關鍵字來同步獲取例項,保證單例的唯一性,但是上面的**在每一次執行時都要進行同步和判斷,無疑會拖慢速度,使用雙重加鎖機制正好可以解決這個問題:

1 public class slhandanli 

4 public static slhandanli getinstance()

10 }

11 }

12 return dl;

13 }

14 }

看了上面的**,有沒有感覺很無語,雙重加鎖難道不是需要兩個synchronized進行加鎖的嗎?

其實不然,這裡的雙重指的的雙重判斷,而加鎖單指那個synchronized,為什麼要進行雙重判斷,其實很簡單,第一重判斷,如果單例已經存在,那麼就不再需要進行同步操作,而是直接返回這個例項,如果沒有建立,才會進入同步塊,同步塊的目的與之前相同,目的是為了防止有兩個呼叫同時進行時,導致生成多個例項,有了同步塊,每次只能有乙個執行緒呼叫能訪問同步塊內容,當第乙個搶到鎖的呼叫獲取了例項之後,這個例項就會被建立,之後的所有呼叫都不會進入同步塊,直接在第一重判斷就返回了單例。至於第二個判斷,個人感覺有點查遺補漏的意味在內(期待高人高見)。

補充:關於鎖內部的第二重空判斷的作用,當多個執行緒一起到達鎖位置時,進行鎖競爭,其中乙個執行緒獲取鎖,如果是第一次進入則dl為null,會進行單例物件的建立,完成後釋放鎖,其他執行緒獲取鎖後就會被空判斷攔截,直接返回已建立的單例物件。

不論如何,使用了雙重加鎖機制後,程式的執行速度有了顯著提公升,不必每次都同步加鎖。

其實我最在意的是volatile的使用,volatile關鍵字的含義是:被其所修飾的變數的值不會被本地執行緒快取,所有對該變數的讀寫都是直接操作共享記憶體來實現,從而確保多個執行緒能正確的處理該變數。該關鍵字可能會遮蔽掉虛擬機器中的一些**優化,所以其執行效率可能不是很高,所以,一般情況下,並不建議使用雙重加鎖機制,酌情使用才是正理!

更進一步說,其實使用volatile的目的是為了防止暴露乙個未初始化的不完整單例例項,導致系統崩潰。因為建立單例例項其實需要經過以下幾步:首先分配記憶體空間、然後將記憶體空間的首位址指向引用(指標),最後呼叫構造器建立例項,由於在第二步的時候這個引用(指標)就會變的非null,那麼在第三步未執行,真正的單例例項還未建立完成的時候,乙個執行緒過來在第乙個校驗中為false,將會直接將不完整的例項返回,從而造成系統崩潰。

3、類級內部類方式

餓漢式會占用較多的空間,因為其在類載入時就會完成例項化,而懶漢式又存在執行速率慢的情況,雙重加鎖機制呢?又有執行效率差的毛病,有沒有一種完美的方式可以規避這些毛病呢?

貌似有的,就是使用類級內部類結合多執行緒預設同步鎖,同時實現延遲載入和執行緒安全。

1 public class classinnerclassdanli 

5 private classinnerclassdanli(){}

6 public static classinnerclassdanli getinstance()

9 }

如上**,所謂類級內部類,就是靜態內部類,這種內部類與其外部類之間並沒有從屬關係,載入外部類的時候,並不會同時載入其靜態內部類,只有在發生呼叫的時候才會進行載入,載入的時候就會建立單例例項並返回,有效實現了懶載入(延遲載入),至於同步問題,我們採用和餓漢式同樣的靜態初始化器的方式,借助jvm來實現執行緒安全。

其實使用靜態初始化器的方式會在類載入時建立類的例項,但是我們將例項的建立顯式放置在靜態內部類中,它會導致在外部類載入時不進行例項建立,這樣就能實現我們的雙重目的:延遲載入和執行緒安全。

4、使用

在spring中建立的bean例項預設都是單例模式存在的。

單例模式及其應用

終極奧義 該類負責建立自己的物件,同時確保只有單個物件被建立。特點 單例類只能有乙個例項。單例類必須自己建立自己的唯一例項。單例類必須給所有其他物件提供這一例項。優點 避免頻繁物件的建立和銷毀,減少記憶體開銷。避免對資源的多重占用 比如寫檔案操作 缺點 沒有介面,不能繼承,與單一職責原則衝突。應用場...

多個單例模式單例模式的應用

我們在程式設計中需要乙個單例,但不僅僅是乙個單例,更多的是需要單例中的單例 即 類a為控制器,類b為例項面板 為方便找到類a,採用單例,而類a為方便找到類b,採用單例中的單例 單例a 單例b 第二個不要用new a.getinstance b.呼叫方法 import b public class a...

多個單例模式單例模式的應用

我們在程式設計中需要乙個單例,但不僅僅是乙個單例,更多的是需要單例中的單例 即 類a為控制器,類b為例項面板 為方便找到類a,採用單例,而類a為方便找到類b,採用單例中的單例 單例a 單例b 第二個不要用new a.getinstance b.呼叫方法 import b public class a...