單例模式 Singleton 簡(jiǎn)單實(shí)例設(shè)計(jì)模式解析
前言
今天我來全面總結(jié)一下Android開發(fā)中最常用的設(shè)計(jì)模式 - 單例模式。
關(guān)于設(shè)計(jì)模式的介紹,可以看下我之前寫的:1分鐘全面了解“設(shè)計(jì)模式”
目錄
1. 引入
1.1 解決的是什么問題
之前說過,設(shè)計(jì)模式 = 某類特定問題的解決方案,那么單例模式是解決什么問題的解決方案呢?
含義:?jiǎn)卫?=一個(gè)實(shí)例;
解決的問題:降低對(duì)象之間的耦合度
解決方法:?jiǎn)卫J剑磳?shí)現(xiàn)一個(gè)類只有一個(gè)實(shí)例化對(duì)象,并提供一個(gè)全局訪問點(diǎn)
1.2 實(shí)例引入
接下來我用一個(gè)實(shí)例來對(duì)單例模式進(jìn)行引入
背景:小成有一個(gè)塑料生產(chǎn)廠,但里面只有一個(gè)倉(cāng)庫(kù)。
目的:想用代碼來實(shí)現(xiàn)倉(cāng)庫(kù)的管理
現(xiàn)有做法: 建立倉(cāng)庫(kù)類和工人類
其中,倉(cāng)庫(kù)類里的quantity=商品數(shù)量;工人類里有搬運(yùn)方法MoveIn(int i)和MoveOut(int i)。
出現(xiàn)的問題:通過測(cè)試發(fā)現(xiàn),每次工人搬運(yùn)操作都會(huì)新建一個(gè)倉(cāng)庫(kù),就是貨物都不是放在同一倉(cāng)庫(kù),這是怎么回事呢?(看下面代碼)
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
package scut.designmodel.SingletonPattern; //倉(cāng)庫(kù)類 class StoreHouse { private int quantity = 100 ; public void setQuantity( int quantity) { this .quantity = quantity; } public int getQuantity() { return quantity; } } //搬貨工人類 class Carrier{ public StoreHouse mStoreHouse; public Carrier(StoreHouse storeHouse){ mStoreHouse = storeHouse; } //搬貨進(jìn)倉(cāng)庫(kù) public void MoveIn( int i){ mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i); } //搬貨出倉(cāng)庫(kù) public void MoveOut( int i){ mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i); } } //工人搬運(yùn)測(cè)試 public class SinglePattern { public static void main(String[] args){ StoreHouse mStoreHouse1 = new StoreHouse(); StoreHouse mStoreHouse2 = new StoreHouse(); Carrier Carrier1 = new Carrier(mStoreHouse1); Carrier Carrier2 = new Carrier(mStoreHouse2); System.out.println( "兩個(gè)是不是同一個(gè)?" ); if (mStoreHouse1.equals(mStoreHouse2)){ //這里用equals而不是用 == 符號(hào),因?yàn)?== 符號(hào)只是比較兩個(gè)對(duì)象的地址 System.out.println( "是同一個(gè)" ); } else { System.out.println( "不是同一個(gè)" ); } //搬運(yùn)工搬完貨物之后出來匯報(bào)倉(cāng)庫(kù)商品數(shù)量 Carrier1.MoveIn( 30 ); System.out.println( "倉(cāng)庫(kù)商品余量:" +Carrier1.mStoreHouse.getQuantity()); Carrier2.MoveOut( 50 ); System.out.println( "倉(cāng)庫(kù)商品余量:" +Carrier2.mStoreHouse.getQuantity()); } } |
結(jié)果:
1
2
3
4
|
兩個(gè)是不是同一個(gè)? 不是同一個(gè) 倉(cāng)庫(kù)商品余量: 130 倉(cāng)庫(kù)商品余量: 50 |
2. 單例模式介紹
2.1 解決的問題(應(yīng)用場(chǎng)景)
沖突:從上面的結(jié)果可以看出,工人類操作的明顯不是同一個(gè)倉(cāng)庫(kù)實(shí)例。
目標(biāo):全部工人操作的是同一個(gè)倉(cāng)庫(kù)實(shí)例
單例模式就是為了解決這類問題的解決方案:實(shí)現(xiàn)一個(gè)類只有一個(gè)實(shí)例化對(duì)象,并提供一個(gè)全局訪問點(diǎn)2.2 工作原理
在Java中,我們通過使用對(duì)象(類實(shí)例化后)來操作這些類,類實(shí)例化是通過它的構(gòu)造方法進(jìn)行的,要是想實(shí)現(xiàn)一個(gè)類只有一個(gè)實(shí)例化對(duì)象,就要對(duì)類的構(gòu)造方法下功夫:
單例模式的一般實(shí)現(xiàn):(含使用步驟)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class Singleton { //1. 創(chuàng)建私有變量 ourInstance(用以記錄 Singleton 的唯一實(shí)例) //2. 內(nèi)部進(jìn)行實(shí)例化 private static Singleton ourInstance = new Singleton(); //3. 把類的構(gòu)造方法私有化,不讓外部調(diào)用構(gòu)造方法實(shí)例化 private Singleton() { } //4. 定義公有方法提供該類的全局唯一訪問點(diǎn) //5. 外部通過調(diào)用getInstance()方法來返回唯一的實(shí)例 public static Singleton newInstance() { return ourInstance; } } |
好了,單例模式的介紹和原理應(yīng)該了解了吧?那么我們現(xiàn)在來解決上面小成出現(xiàn)的“倉(cāng)庫(kù)不是一個(gè)”的問題吧!
2.3 實(shí)例介紹
小成使用單例模式改善上面例子的代碼:
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
package scut.designmodel.SingletonPattern; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //單例倉(cāng)庫(kù)類 class StoreHouse { //倉(cāng)庫(kù)商品數(shù)量 private int quantity = 100 ; //自己在內(nèi)部實(shí)例化 private static StoreHouse ourInstance = new StoreHouse();; //讓外部通過調(diào)用getInstance()方法來返回唯一的實(shí)例。 public static StoreHouse getInstance() { return ourInstance; } //封閉構(gòu)造函數(shù) private StoreHouse() { } public void setQuantity( int quantity) { this .quantity = quantity; } public int getQuantity() { return quantity; } } //搬貨工人類 class Carrier{ public StoreHouse mStoreHouse; public Carrier(StoreHouse storeHouse){ mStoreHouse = storeHouse; } //搬貨進(jìn)倉(cāng)庫(kù) public void MoveIn( int i){ mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i); } //搬貨出倉(cāng)庫(kù) public void MoveOut( int i){ mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i); } } //工人搬運(yùn)測(cè)試 public class SinglePattern { public static void main(String[] args){ StoreHouse mStoreHouse1 = StoreHouse.getInstance(); StoreHouse mStoreHouse2 = StoreHouse.getInstance(); Carrier Carrier1 = new Carrier(mStoreHouse1); Carrier Carrier2 = new Carrier(mStoreHouse2); System.out.println( "兩個(gè)是不是同一個(gè)?" ); if (mStoreHouse1.equals(mStoreHouse2)){ System.out.println( "是同一個(gè)" ); } else { System.out.println( "不是同一個(gè)" ); } //搬運(yùn)工搬完貨物之后出來匯報(bào)倉(cāng)庫(kù)商品數(shù)量 Carrier1.MoveIn( 30 ); System.out.println( "倉(cāng)庫(kù)商品余量:" +Carrier1.mStoreHouse.getQuantity()); Carrier2.MoveOut( 50 ); System.out.println( "倉(cāng)庫(kù)商品余量:" +Carrier2.mStoreHouse.getQuantity()); } } |
結(jié)果:
1
2
3
4
|
兩個(gè)是不是同一個(gè)? 是同一個(gè) 倉(cāng)庫(kù)商品余量: 130 倉(cāng)庫(kù)商品余量: 80 |
從結(jié)果分析,使用了單例模式后,倉(cāng)庫(kù)類就只有一個(gè)倉(cāng)庫(kù)實(shí)例了,再也不用擔(dān)心搬運(yùn)工人進(jìn)錯(cuò)倉(cāng)庫(kù)了!!!
2.4 優(yōu)點(diǎn)
- 提供了對(duì)唯一實(shí)例的受控訪問;
- 由于在系統(tǒng)內(nèi)存中只存在一個(gè)對(duì)象,因此可以節(jié)約系統(tǒng)資源,對(duì)于一些需要頻繁創(chuàng)建和銷毀的對(duì)象單例模式無(wú)疑可以提高系統(tǒng)的性能;
- 可以根據(jù)實(shí)際情況需要,在單例模式的基礎(chǔ)上擴(kuò)展做出雙例模式,多例模式;
2.5 缺點(diǎn)
- 單例類的職責(zé)過重,里面的代碼可能會(huì)過于復(fù)雜,在一定程度上違背了“單一職責(zé)原則”。
- 如果實(shí)例化的對(duì)象長(zhǎng)時(shí)間不被利用,會(huì)被系統(tǒng)認(rèn)為是垃圾而被回收,這將導(dǎo)致對(duì)象狀態(tài)的丟失。
3. 單例模式的實(shí)現(xiàn)方式
3.1 一般情況
餓漢式(最簡(jiǎn)單的單例實(shí)現(xiàn)方式)
1
2
3
4
5
6
7
8
9
10
|
class Singleton { private static Singleton ourInstance = new Singleton(); private Singleton() { } public static Singleton newInstance() { return ourInstance; } } |
應(yīng)用場(chǎng)景:
- 要求直接在應(yīng)用啟動(dòng)時(shí)加載并初始化
- 單例對(duì)象要求初始化速度非常快且占用內(nèi)存非常小
懶漢式
懶漢式與餓漢式最大的區(qū)別是單例的初始化操作的時(shí)機(jī):
- 餓漢式:自動(dòng)進(jìn)行單例的初始化
- 懶漢式:有需要的時(shí)候才手動(dòng)調(diào)用newInstance()進(jìn)行單例的初始化操作
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Singleton { private static Singleton ourInstance = null ; private Singleton() { } public static Singleton newInstance() { if ( ourInstance == null ){ ourInstance = new Singleton(); } return ourInstance; } } |
應(yīng)用場(chǎng)景:
- 單例初始化的操作耗時(shí)比較長(zhǎng)而應(yīng)用對(duì)于啟動(dòng)速度又有要求
- 單例的占用內(nèi)存比較大
- 單例只是在某個(gè)特定場(chǎng)景的情況下才會(huì)被使用,即按需延遲加載單例。
3.2 多線程下的單例模式實(shí)現(xiàn)
在多線程的情況下:
- 對(duì)于“餓漢式單例模式”:適用,因?yàn)镴VM只會(huì)加載一次單例類;
- 對(duì)于“懶漢式單例模式”:不適用,因?yàn)?ldquo;懶漢式”在創(chuàng)建單例時(shí)是線程不安全的,多個(gè)線程可能會(huì)并發(fā)調(diào)用 newInstance 方法從而出現(xiàn)重復(fù)創(chuàng)建單例對(duì)象的問題。
解決方案1:同步鎖
使用同步鎖 synchronized (Singleton.class) 防止多線程同時(shí)進(jìn)入造成instance被多次實(shí)例化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Singleton { private static Singleton ourInstance = null ; private Singleton() { } public static Singleton newInstance() { synchronized (Singleton. class ){ if ( ourInstance == null ){ ourInstance = new Singleton(); } } return ourInstance; } } |
解決方案2:雙重校驗(yàn)鎖
在同步鎖的基礎(chǔ)上( synchronized (Singleton.class) 外)添加了一層if,這是為了在Instance已經(jīng)實(shí)例化后下次進(jìn)入不必執(zhí)行 synchronized (Singleton.class) 獲取對(duì)象鎖,從而提高性能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Singleton { private static Singleton ourInstance = null ; private Singleton() { } public static Singleton newInstance() { if ( ourInstance == null ){ synchronized (Singleton. class ){ if ( ourInstance == null ){ ourInstance = new Singleton(); } } } return ourInstance; } } |
解決方案3:靜態(tài)內(nèi)部類
在JVM進(jìn)行類加載的時(shí)候會(huì)保證數(shù)據(jù)是同步的,我們采用內(nèi)部類實(shí)現(xiàn):在內(nèi)部類里面去創(chuàng)建對(duì)象實(shí)例。
只要應(yīng)用中不使用內(nèi)部類 JVM 就不會(huì)去加載這個(gè)單例類,也就不會(huì)創(chuàng)建單例對(duì)象,從而實(shí)現(xiàn)“懶漢式”的延遲加載和線程安全。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Singleton { //在裝載該內(nèi)部類時(shí)才會(huì)去創(chuàng)建單例對(duì)象 private static class Singleton2{ private static Singleton ourInstance = new Singleton(); } private Singleton() { } public static Singleton newInstance() { return Singleton2.ourInstance; } } |
解決方案4:枚舉類型
最簡(jiǎn)潔、易用的單例實(shí)現(xiàn)方式,(《Effective Java》推薦)
1
2
3
4
5
6
7
8
9
|
public enum Singleton{ //定義一個(gè)枚舉的元素,它就是Singleton的一個(gè)實(shí)例 instance; public void doSomething(){ } } |
使用方式如下:
1
2
|
Singleton singleton = Singleton.instance; singleton.doSomething(); |
5. 總結(jié)
本文主要對(duì)單例模式進(jìn)行了全面介紹,包括原理和實(shí)現(xiàn)方式,接下來我會(huì)繼續(xù)講解其他設(shè)計(jì)模式,有興趣可以繼續(xù)關(guān)注
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
原文鏈接:http://blog.csdn.net/carson_ho/article/details/52223097