單例模式應該是設計模式中比較簡單的一個,也是很是常見的,可是在多線程併發的環境下使用倒是不那麼簡單了,今天給你們分享一個我在開發過程當中遇到的單例模式的應用。html
首先咱們先來看一下單例模式的定義:java
一個類有且僅有一個實例,而且自行實例化向整個系統提供。
單例模式的要素:
1.私有的靜態的實例對象
2.私有的構造函數(保證在該類外部,沒法經過new的方式來建立對象實例)
3.公有的、靜態的、訪問該實例對象的方法docker
單例模式分爲懶漢形和餓漢式編程
懶漢式:設計模式
應用剛啓動的時候,並不建立實例,當外部調用該類的實例或者該類實例方法的時候,才建立該類的實例。(時間換空間)安全
優勢:實例在被使用的時候才被建立,能夠節省系統資源,體現了延遲加載的思想。性能優化
缺點:因爲系統剛啓動時且未被外部調用時,實例沒有建立;若是一時間有多個線程同時調用LazySingleton.getLazyInstance()方法頗有可能會產生多個實例。多線程
例子:架構
1
2
3
4
5
6
7
8
9
10
11
12
|
publicclassSingletonClass{
//私有構造函數,保證類不能經過new建立
privateSingletonClass(){}
privatestaticSingletonClassinstance=
null
;
publicstaticSingletonClassgetInstance(){
if
(instance==
null
){
//建立本類對象
instance=newSingletonClass();
}
returninstance;
}
}
|
餓漢式:併發
應用剛啓動的時候,無論外部有沒有調用該類的實例方法,該類的實例就已經建立好了。(空間換時間。)
優勢:寫法簡單,在多線程下也能保證單例實例的惟一性,不用同步,運行效率高。
缺點:在外部沒有使用到該類的時候,該類的實例就建立了,若該類實例的建立比較消耗系統資源,而且外部一直沒有調用該實例,那麼這部分的系統資源的消耗是沒有意義的。
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
publicclassSingleton{
//首先本身在內部定義本身的一個實例,只供內部調用
privatestaticfinalSingletoninstance=newSingleton();
//私有構造函數
privateSingleton(){
}
//提供了靜態方法,外部能夠直接調用
publicstaticSingletongetInstance(){
returninstance;
}
}
下面模擬單例模式在多線程下會出現的問題
/**
*懶漢式單例類
*/
publicclassLazySingleton{
//爲了易於模擬多線程下,懶漢式出現的問題,咱們在建立實例的構造函數裏面使當前線程暫停了50毫秒
privateLazySingleton(){
try
{
Thread.sleep(
50
);
}
catch
(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(
"生成LazySingleton實例一次!"
);
}
privatestaticLazySingletonlazyInstance=
null
;
publicstaticLazySingletongetLazyInstance(){
if
(lazyInstance==
null
){
lazyInstance=newLazySingleton();
}
returnlazyInstance;
}
}
|
測試代碼:咱們在測試代碼裏面新建了10個線程,讓這10個線程同時調用LazySingleton.getLazyInstance()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
publicclassSingletonTest{
publicstaticvoidmain(String[]args){
//建立十個線程調
for
(inti=
0
;i<
10
;i++){
newThread(){
@Override
publicvoidrun(){
LazySingleton.getLazyInstance();
}
}.start();
}
}
}
|
結果:
生成LazySingleton實例一次!
生成LazySingleton實例一次!
生成LazySingleton實例一次!
生成LazySingleton實例一次!
生成LazySingleton實例一次!
生成LazySingleton實例一次!
生成LazySingleton實例一次!
生成LazySingleton實例一次!
生成LazySingleton實例一次!
生成LazySingleton實例一次!
能夠看出單例模式懶漢式在多線程的併發下也會出現問題,
分析一下:多個線程同時訪問上面的懶漢式單例,如今有兩個線程A和B同時訪問LazySingleton.getLazyInstance()方法。
假設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使其同步,可是這樣一來,會下降整個訪問的速度,並且每次都要判斷。
那麼有沒有更好的方式來實現呢?咱們能夠考慮使用"雙重檢查加鎖"的方式來實現,就能夠既實現線程安全,又可以使性能不受到很大的影響。咱們看看具體解決代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
publicclassLazySingleton{
privateLazySingleton(){
try
{
Thread.sleep(
50
);
}
catch
(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(
"生成LazySingleton實例一次!"
);
}
privatestaticLazySingletonlazyInstance=
null
;
publicstaticLazySingletongetLazyInstance(){
//先檢查實例是否存在,若是不存在才進入下面的同步塊
if
(lazyInstance==
null
){
//同步塊,線程安全地建立實例
synchronized
(LazySingleton.
class
){
//再次檢查實例是否存在,若是不存在才真正地建立實例
if
(lazyInstance==
null
){
lazyInstance=newLazySingleton();
}
}
}
returnlazyInstance;
}
}
|
這樣咱們就能夠在多線程併發下安全應用單例模式中的懶漢模式。這種方法在代碼上可能就不怎麼美觀,咱們能夠優雅的使用一個內部類來維護單例類的實例,下面看看代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
|
publicclassGracefulSingleton{
privateGracefulSingleton(){
System.out.println(
"建立GracefulSingleton實例一次!"
);
}
//類級的內部類,也就是靜態的成員式內部類,該內部類的實例與外部類的實例沒有綁定關係,並且只有被調用到纔會裝載,從而實現了延遲加載
privatestaticclassSingletonHoder{
//靜態初始化器,由JVM來保證線程安全
privatestaticGracefulSingletoninstance=newGracefulSingleton();
}
publicstaticGracefulSingletongetInstance(){
returnSingletonHoder.instance;
}
}
|
說一下我在實際開發中的場景:爲了程序的高效率使用多線程併發,然而是循環調用,可能致使建立線程數過多,考慮採用線程池管理,這時候建立線程池仍然是處於循環調用中,也可能致使多個線程池,這時候就考慮使用單例模式。
源代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
publicclassThreadPoolFactoryUtil{
privateExecutorServiceexecutorService;
//在構造函數中建立線程池
privateThreadPoolFactoryUtil(){
//獲取系統處理器個數,做爲線程池數量
intnThreads=Runtime.getRuntime().availableProcessors();
executorService=Executors.newFixedThreadPool(nThreads);
}
//定義一個靜態內部類,內部定義靜態成員建立外部類實例
privatestaticclassSingletonContainer{
privatestaticThreadPoolFactoryUtilutil=newThreadPoolFactoryUtil();
}
//獲取本類對象
publicstaticThreadPoolFactoryUtilgetUtil(){
returnSingletonContainer.util;
}
publicExecutorServicegetExecutorService(){
returnexecutorService;
}
}
|
涉及到一個靜態內部類,咱們看看靜態內部類的特色:
一、靜態內部類無需依賴於外部類,它能夠獨立於外部對象而存在。
二、靜態內部類,多個外部類的對象能夠共享同一個內部類的對象。
三、使用靜態內部類的好處是增強了代碼的封裝性以及提升了代碼的可讀性。
四、普通內部類不能聲明static的方法和變量,注意這裏說的是變量,常量(也就是finalstatic修飾的屬性)仍是能夠的,而靜態內部類形似外部類,沒有任何限制。能夠直接被用外部類名+內部類名得到。