前言
今天Carson來全面總結最常用的設計模式 - 單例模式。
其他設計模式介紹
1分鐘全面了解“設計模式”
單例模式(Singleton) - 最易懂的設計模式解析
簡單工廠模式(SimpleFactoryPattern)- 最易懂的設計模式解析
工廠方法模式(Factory Method)- 最易懂的設計模式解析
抽象工廠模式(Abstract Factory)- 最易懂的設計模式解析
策略模式(Strategy Pattern)- 最易懂的設計模式解析
適配器模式(Adapter Pattern)- 最易懂的設計模式解析
代理模式(Proxy Pattern)- 最易懂的設計模式解析
模板方法模式(Template Method) - 最易懂的設計模式解析
建造者模式(Builder Pattern)- 最易懂的設計模式解析
外觀模式(Facade Pattern) - 最易懂的設計模式解析
目錄
1. 實例引入
- 背景:小成有一個塑料生產(chǎn)廠,但里面只有一個倉庫。
- 目的:想用代碼來實現(xiàn)倉庫的管理
- 現(xiàn)有做法: 建立倉庫類和工人類
其中,倉庫類里的quantity=商品數(shù)量;工人類里有搬運方法MoveIn(int i)和MoveOut(int i)。
- 出現(xiàn)的問題:通過測試發(fā)現(xiàn),每次工人搬運操作都會新建一個倉庫,就是貨物都不是放在同一倉庫,這是怎么回事呢?(看下面代碼)
package scut.designmodel.SingletonPattern;
//倉庫類
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;
}
//搬貨進倉庫
public void MoveIn(int i){
mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i);
}
//搬貨出倉庫
public void MoveOut(int i){
mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i);
}
}
//工人搬運測試
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("兩個是不是同一個?");
if(mStoreHouse1.equals(mStoreHouse2)){//這里用equals而不是用 == 符號,因為 == 符號只是比較兩個對象的地址
System.out.println("是同一個");
}else {
System.out.println("不是同一個");
}
//搬運工搬完貨物之后出來匯報倉庫商品數(shù)量
Carrier1.MoveIn(30);
System.out.println("倉庫商品余量:"+Carrier1.mStoreHouse.getQuantity());
Carrier2.MoveOut(50);
System.out.println("倉庫商品余量:"+Carrier2.mStoreHouse.getQuantity());
}
}
結果:
兩個是不是同一個?
不是同一個
倉庫商品余量:130
倉庫商品余量:50
2. 單例模式介紹
2.1 模式說明
實現(xiàn)1個類只有1個實例化對象 & 提供一個全局訪問點
2.2 作用(解決的問題)
保證1個類只有1個對象,降低對象之間的耦合度
從上面可看出:工人類操作的明顯不是同一個倉庫實例,而全部工人希望操作的是同一個倉庫實例,即只有1個實例
2.3 工作原理
在Java中,我們通過使用對象(類實例化后)來操作這些類,類實例化是通過它的構造方法進行的,要是想實現(xiàn)一個類只有一個實例化對象,就要對類的構造方法下功夫:
單例模式的一般實現(xiàn):(含使用步驟)
public class Singleton {
//1. 創(chuàng)建私有變量 ourInstance(用以記錄 Singleton 的唯一實例)
//2. 內(nèi)部進行實例化
private static Singleton ourInstance = new Singleton();
//3. 把類的構造方法私有化,不讓外部調(diào)用構造方法實例化
private Singleton() {
}
//4. 定義公有方法提供該類的全局唯一訪問點
//5. 外部通過調(diào)用getInstance()方法來返回唯一的實例
public static Singleton newInstance() {
return ourInstance;
}
}
好了,單例模式的介紹和原理應該了解了吧?那么我們現(xiàn)在來解決上面小成出現(xiàn)的“倉庫不是一個”的問題吧!
3. 實例講解
小成使用單例模式改善上面例子的代碼:
package scut.designmodel.SingletonPattern;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//單例倉庫類
class StoreHouse {
//倉庫商品數(shù)量
private int quantity = 100;
//自己在內(nèi)部實例化
private static StoreHouse ourInstance = new StoreHouse();;
//讓外部通過調(diào)用getInstance()方法來返回唯一的實例。
public static StoreHouse getInstance() {
return ourInstance;
}
//封閉構造函數(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;
}
//搬貨進倉庫
public void MoveIn(int i){
mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i);
}
//搬貨出倉庫
public void MoveOut(int i){
mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i);
}
}
//工人搬運測試
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("兩個是不是同一個?");
if(mStoreHouse1.equals(mStoreHouse2)){
System.out.println("是同一個");
}else {
System.out.println("不是同一個");
}
//搬運工搬完貨物之后出來匯報倉庫商品數(shù)量
Carrier1.MoveIn(30);
System.out.println("倉庫商品余量:"+Carrier1.mStoreHouse.getQuantity());
Carrier2.MoveOut(50);
System.out.println("倉庫商品余量:"+Carrier2.mStoreHouse.getQuantity());
}
}
結果:
兩個是不是同一個?
是同一個
倉庫商品余量:130
倉庫商品余量:80
從結果分析,使用了單例模式后,倉庫類就只有一個倉庫實例了,再也不用擔心搬運工人進錯倉庫了!!!
4. 特點
4.1 優(yōu)點
- 提供了對唯一實例的受控訪問;
- 由于在系統(tǒng)內(nèi)存中只存在一個對象,因此可以節(jié)約系統(tǒng)資源,對于一些需要頻繁創(chuàng)建和銷毀的對象單例模式無疑可以提高系統(tǒng)的性能;
- 可以根據(jù)實際情況需要,在單例模式的基礎上擴展做出雙例模式,多例模式;
4.2 缺點
- 單例類的職責過重,里面的代碼可能會過于復雜,在一定程度上違背了“單一職責原則”。
- 如果實例化的對象長時間不被利用,會被系統(tǒng)認為是垃圾而被回收,這將導致對象狀態(tài)的丟失。
5. 單例模式的實現(xiàn)方式
- 單例模式的實現(xiàn)方式有多種,根據(jù)需求場景,可分為2大類、6種實現(xiàn)方式。具體如下:
- 下面,我將詳細介紹每種單例模式的實現(xiàn)方式
a. 初始化單例類時 即 創(chuàng)建單例
1. 餓漢式
這是 最簡單的單例實現(xiàn)方式
- 原理
依賴JVM
類加載機制,保證單例只會被創(chuàng)建1次,即 線程安全
JVM
在類的初始化階段(即 在Class
被加載后、被線程使用前),會執(zhí)行類的初始化- 在執(zhí)行類的初始化期間,JVM會去獲取一個鎖。這個鎖可以同步多個線程對同一個類的初始化
- 具體實現(xiàn)
class Singleton {
// 1. 加載該類時,單例就會自動被創(chuàng)建
private static Singleton ourInstance = new Singleton();
// 2. 構造函數(shù) 設置為 私有權限
// 原因:禁止他人創(chuàng)建實例
private Singleton() {
}
// 3. 通過調(diào)用靜態(tài)方法獲得創(chuàng)建的單例
public static Singleton newInstance() {
return ourInstance;
}
}
- 應用場景
除了初始化單例類時 即 創(chuàng)建單例外,繼續(xù)延伸出來的是:單例對象 要求初始化速度快 & 占用內(nèi)存小
2. 枚舉類型
- 原理
根據(jù)枚舉類型的下述特點,滿足單例模式所需的 創(chuàng)建單例、線程安全、實現(xiàn)簡潔的需求
- 實現(xiàn)方式
public enum Singleton{
//定義1個枚舉的元素,即為單例類的1個實例
INSTANCE;
// 隱藏了1個空的、私有的 構造方法
// private Singleton () {}
}
// 獲取單例的方式:
Singleton singleton = Singleton.INSTANCE;
- 注:這是 最簡潔、易用 的單例實現(xiàn)方式,借用
《Effective Java》
的話:
單元素的枚舉類型已經(jīng)成為實現(xiàn)
Singleton
的最佳方法
b. 按需、延遲創(chuàng)建單例
1. 懶漢式(基礎實現(xiàn))
- 原理
與 餓漢式 最大的區(qū)別是:單例創(chuàng)建的時機
- 餓漢式:單例創(chuàng)建時機不可控,即類加載時 自動創(chuàng)建 單例
- 懶漢式:單例創(chuàng)建時機可控,即有需要時,才 手動創(chuàng)建 單例
- 具體實現(xiàn)
class Singleton {
// 1. 類加載時,先不自動創(chuàng)建單例
// 即,將單例的引用先賦值為 Null
private static Singleton ourInstance = null;
// 2. 構造函數(shù) 設置為 私有權限
// 原因:禁止他人創(chuàng)建實例
private Singleton() {
}
// 3. 需要時才手動調(diào)用 newInstance() 創(chuàng)建 單例
public static Singleton newInstance() {
// 先判斷單例是否為空,以避免重復創(chuàng)建
if( ourInstance == null){
ourInstance = new Singleton();
}
return ourInstance;
}
}
- 缺點
基礎實現(xiàn)的懶漢式是線程不安全的,具體原因如下
- 下面,將對懶漢式 進行優(yōu)化,使得適合在多線程環(huán)境下運行
2. 同步鎖(懶漢式的改進)
- 原理
使用同步鎖synchronized
鎖住 創(chuàng)建單例的方法 ,防止多個線程同時調(diào)用,從而避免造成單例被多次創(chuàng)建
- 即,
getInstance()
方法塊只能運行在1個線程中- 若該段代碼已在1個線程中運行,另外1個線程試圖運行該塊代碼,則 會被阻塞而一直等待
- 而在這個線程安全的方法里我們實現(xiàn)了單例的創(chuàng)建,保證了多線程模式下 單例對象的唯一性
- 具體實現(xiàn)
// 寫法1
class Singleton {
// 1. 類加載時,先不自動創(chuàng)建單例
// 即,將單例的引用先賦值為 Null
private static Singleton ourInstance = null;
// 2. 構造函數(shù) 設置為 私有權限
// 原因:禁止他人創(chuàng)建實例
private Singleton() {
}
// 3. 加入同步鎖
public static synchronized Singleton getInstance(){
// 先判斷單例是否為空,以避免重復創(chuàng)建
if ( ourInstance == null )
ourInstance = new Singleton();
return ourInstance;
}
}
// 寫法2
// 該寫法的作用與上述寫法作用相同,只是寫法有所區(qū)別
class Singleton{
private static Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
// 加入同步鎖
synchronized(Singleton.class) {
if (instance == null)
instance = new Singleton();
}
return instance;
}
}
- 缺點
每次訪問都要進行線程同步(即 調(diào)用synchronized
鎖),造成過多的同步開銷(加鎖 = 耗時、耗能)
實際上只需在第1次調(diào)用該方法時才需要同步,一旦單例創(chuàng)建成功后,就沒必要進行同步
3. 雙重校驗鎖(懶漢式的改進)
原理
在同步鎖的基礎上,添加1層if
判斷:若單例已創(chuàng)建,則不需再執(zhí)行加鎖操作就可獲取實例,從而提高性能具體實現(xiàn)
class Singleton {
private static Singleton ourInstance = null;
private Singleton() {
}
public static Singleton newInstance() {
// 加入雙重校驗鎖
// 校驗鎖1:第1個if
if( ourInstance == null){ // ①
synchronized (Singleton.class){ // ②
// 校驗鎖2:第2個 if
if( ourInstance == null){
ourInstance = new Singleton();
}
}
}
return ourInstance;
}
}
// 說明
// 校驗鎖1:第1個if
// 作用:若單例已創(chuàng)建,則直接返回已創(chuàng)建的單例,無需再執(zhí)行加鎖操作
// 即直接跳到執(zhí)行 return ourInstance
// 校驗鎖2:第2個 if
// 作用:防止多次創(chuàng)建單例問題
// 原理
// 1. 線程A調(diào)用newInstance(),當運行到②位置時,此時線程B也調(diào)用了newInstance()
// 2. 因線程A并沒有執(zhí)行instance = new Singleton();,此時instance仍為空,因此線程B能突破第1層 if 判斷,運行到①位置等待synchronized中的A線程執(zhí)行完畢
// 3. 當線程A釋放同步鎖時,單例已創(chuàng)建,即instance已非空
// 4. 此時線程B 從①開始執(zhí)行到位置②。此時第2層 if 判斷 = 為空(單例已創(chuàng)建),因此也不會創(chuàng)建多余的實例
- 缺點
實現(xiàn)復雜 = 多種判斷,易出錯
4. 靜態(tài)內(nèi)部類
- 原理
根據(jù) 靜態(tài)內(nèi)部類 的特性,同時解決了按需加載、線程安全的問題,同時實現(xiàn)簡潔
- 在靜態(tài)內(nèi)部類里創(chuàng)建單例,在裝載該內(nèi)部類時才會去創(chuàng)建單例
- 線程安全:類是由
JVM
加載,而JVM
只會加載1遍,保證只有1個單例
- 具體實現(xiàn)
class Singleton {
// 1. 創(chuàng)建靜態(tài)內(nèi)部類
private static class Singleton2 {
// 在靜態(tài)內(nèi)部類里創(chuàng)建單例
private static Singleton ourInstance = new Singleton();
}
// 私有構造函數(shù)
private Singleton() {
}
// 延遲加載、按需創(chuàng)建
public static Singleton newInstance() {
return Singleton2.ourInstance;
}
}
// 調(diào)用過程說明:
// 1. 外部調(diào)用類的newInstance()
// 2. 自動調(diào)用Singleton2.ourInstance
// 2.1 此時單例類Singleton2得到初始化
// 2.2 而該類在裝載 & 被初始化時,會初始化它的靜態(tài)域,從而創(chuàng)建單例;
// 2.3 由于是靜態(tài)域,因此只會JVM只會加載1遍,Java虛擬機保證了線程安全性
// 3. 最終只創(chuàng)建1個單例
6. 總結
- 本文主要對 單例模式 進行了全面介紹,包括原理 & 實現(xiàn)方式
- 對于實現(xiàn)方式,此處作出總結
接下來我會對每種設計模式進行詳細的分析,歡迎關注Carson_Ho的簡書,不定期分享關于安卓開發(fā)的干貨,追求短、平、快,但卻不缺深度。
請點贊!因為你的鼓勵是我寫作的最大動力!
相關文章閱讀
單例模式(Singleton) - 最易懂的設計模式解析
簡單工廠模式(SimpleFactoryPattern)- 最易懂的設計模式解析
工廠方法模式(Factory Method)- 最易懂的設計模式解析
抽象工廠模式(Abstract Factory)- 最易懂的設計模式解析
策略模式(Strategy Pattern)- 最易懂的設計模式解析
適配器模式(Adapter Pattern)- 最易懂的設計模式解析
代理模式(Proxy Pattern)- 最易懂的設計模式解析
模板方法模式(Template Method) - 最易懂的設計模式解析
建造者模式(Builder Pattern)- 最易懂的設計模式解析
外觀模式(Facade Pattern) - 最易懂的設計模式解析