Android 單例模式 Singleton 簡單實(shí)例設(shè)計(jì)模式解析
單例模式 Singleton 簡單實(shí)例設(shè)計(jì)模式解析
前言
今天我來全面總結(jié)一下Android開發(fā)中最常用的設(shè)計(jì)模式 - 單例模式。
關(guān)于設(shè)計(jì)模式的介紹,可以看下我之前寫的:1分鐘全面了解“設(shè)計(jì)模式”
目錄

1. 引入
1.1 解決的是什么問題
之前說過,設(shè)計(jì)模式 = 某類特定問題的解決方案,那么單例模式是解決什么問題的解決方案呢?
含義:單例 =一個(gè)實(shí)例;
解決的問題:降低對象之間的耦合度
解決方法:單例模式,即實(shí)現(xiàn)一個(gè)類只有一個(gè)實(shí)例化對象,并提供一個(gè)全局訪問點(diǎn)
1.2 實(shí)例引入
接下來我用一個(gè)實(shí)例來對單例模式進(jìn)行引入
背景:小成有一個(gè)塑料生產(chǎn)廠,但里面只有一個(gè)倉庫。
目的:想用代碼來實(shí)現(xiàn)倉庫的管理
現(xiàn)有做法: 建立倉庫類和工人類
其中,倉庫類里的quantity=商品數(shù)量;工人類里有搬運(yùn)方法MoveIn(int i)和MoveOut(int i)。
出現(xiàn)的問題:通過測試發(fā)現(xiàn),每次工人搬運(yùn)操作都會新建一個(gè)倉庫,就是貨物都不是放在同一倉庫,這是怎么回事呢?(看下面代碼)
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;
}
//搬貨進(jìn)倉庫
public void MoveIn(int i){
mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i);
}
//搬貨出倉庫
public void MoveOut(int i){
mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i);
}
}
//工人搬運(yùn)測試
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而不是用 == 符號,因?yàn)?== 符號只是比較兩個(gè)對象的地址
System.out.println("是同一個(gè)");
}else {
System.out.println("不是同一個(gè)");
}
//搬運(yùn)工搬完貨物之后出來匯報(bào)倉庫商品數(shù)量
Carrier1.MoveIn(30);
System.out.println("倉庫商品余量:"+Carrier1.mStoreHouse.getQuantity());
Carrier2.MoveOut(50);
System.out.println("倉庫商品余量:"+Carrier2.mStoreHouse.getQuantity());
}
}
結(jié)果:
兩個(gè)是不是同一個(gè)? 不是同一個(gè) 倉庫商品余量:130 倉庫商品余量:50
2. 單例模式介紹
2.1 解決的問題(應(yīng)用場景)
沖突:從上面的結(jié)果可以看出,工人類操作的明顯不是同一個(gè)倉庫實(shí)例。
目標(biāo):全部工人操作的是同一個(gè)倉庫實(shí)例
單例模式就是為了解決這類問題的解決方案:實(shí)現(xiàn)一個(gè)類只有一個(gè)實(shí)例化對象,并提供一個(gè)全局訪問點(diǎn)2.2 工作原理
在Java中,我們通過使用對象(類實(shí)例化后)來操作這些類,類實(shí)例化是通過它的構(gòu)造方法進(jìn)行的,要是想實(shí)現(xiàn)一個(gè)類只有一個(gè)實(shí)例化對象,就要對類的構(gòu)造方法下功夫:

單例模式的一般實(shí)現(xiàn):(含使用步驟)
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)的“倉庫不是一個(gè)”的問題吧!
2.3 實(shí)例介紹
小成使用單例模式改善上面例子的代碼:
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)部實(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)倉庫
public void MoveIn(int i){
mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i);
}
//搬貨出倉庫
public void MoveOut(int i){
mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i);
}
}
//工人搬運(yùn)測試
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)倉庫商品數(shù)量
Carrier1.MoveIn(30);
System.out.println("倉庫商品余量:"+Carrier1.mStoreHouse.getQuantity());
Carrier2.MoveOut(50);
System.out.println("倉庫商品余量:"+Carrier2.mStoreHouse.getQuantity());
}
}
結(jié)果:
兩個(gè)是不是同一個(gè)? 是同一個(gè) 倉庫商品余量:130 倉庫商品余量:80
從結(jié)果分析,使用了單例模式后,倉庫類就只有一個(gè)倉庫實(shí)例了,再也不用擔(dān)心搬運(yùn)工人進(jìn)錯(cuò)倉庫了?。。?/p>
2.4 優(yōu)點(diǎn)
- 提供了對唯一實(shí)例的受控訪問;
- 由于在系統(tǒng)內(nèi)存中只存在一個(gè)對象,因此可以節(jié)約系統(tǒng)資源,對于一些需要頻繁創(chuàng)建和銷毀的對象單例模式無疑可以提高系統(tǒng)的性能;
- 可以根據(jù)實(shí)際情況需要,在單例模式的基礎(chǔ)上擴(kuò)展做出雙例模式,多例模式;
2.5 缺點(diǎn)
- 單例類的職責(zé)過重,里面的代碼可能會過于復(fù)雜,在一定程度上違背了“單一職責(zé)原則”。
- 如果實(shí)例化的對象長時(shí)間不被利用,會被系統(tǒng)認(rèn)為是垃圾而被回收,這將導(dǎo)致對象狀態(tài)的丟失。
3. 單例模式的實(shí)現(xiàn)方式
3.1 一般情況
餓漢式(最簡單的單例實(shí)現(xiàn)方式)
class Singleton {
private static Singleton ourInstance = new Singleton();
private Singleton() {
}
public static Singleton newInstance() {
return ourInstance;
}
}
應(yīng)用場景:
- 要求直接在應(yīng)用啟動(dòng)時(shí)加載并初始化
- 單例對象要求初始化速度非常快且占用內(nèi)存非常小
懶漢式
懶漢式與餓漢式最大的區(qū)別是單例的初始化操作的時(shí)機(jī):
- 餓漢式:自動(dòng)進(jìn)行單例的初始化
- 懶漢式:有需要的時(shí)候才手動(dòng)調(diào)用newInstance()進(jìn)行單例的初始化操作
class Singleton {
private static Singleton ourInstance = null;
private Singleton() {
}
public static Singleton newInstance() {
if( ourInstance == null){
ourInstance = new Singleton();
}
return ourInstance;
}
}
應(yīng)用場景:
- 單例初始化的操作耗時(shí)比較長而應(yīng)用對于啟動(dòng)速度又有要求
- 單例的占用內(nèi)存比較大
- 單例只是在某個(gè)特定場景的情況下才會被使用,即按需延遲加載單例。
3.2 多線程下的單例模式實(shí)現(xiàn)
在多線程的情況下:
- 對于“餓漢式單例模式”:適用,因?yàn)镴VM只會加載一次單例類;
- 對于“懶漢式單例模式”:不適用,因?yàn)椤皯袧h式”在創(chuàng)建單例時(shí)是線程不安全的,多個(gè)線程可能會并發(fā)調(diào)用 newInstance 方法從而出現(xiàn)重復(fù)創(chuàng)建單例對象的問題。
解決方案1:同步鎖
使用同步鎖 synchronized (Singleton.class) 防止多線程同時(shí)進(jìn)入造成instance被多次實(shí)例化。
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) 獲取對象鎖,從而提高性能。
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í)候會保證數(shù)據(jù)是同步的,我們采用內(nèi)部類實(shí)現(xiàn):在內(nèi)部類里面去創(chuàng)建對象實(shí)例。
只要應(yīng)用中不使用內(nèi)部類 JVM 就不會去加載這個(gè)單例類,也就不會創(chuàng)建單例對象,從而實(shí)現(xiàn)“懶漢式”的延遲加載和線程安全。
class Singleton {
//在裝載該內(nèi)部類時(shí)才會去創(chuàng)建單例對象
private static class Singleton2{
private static Singleton ourInstance = new Singleton();
}
private Singleton() {
}
public static Singleton newInstance() {
return Singleton2.ourInstance;
}
}
解決方案4:枚舉類型
最簡潔、易用的單例實(shí)現(xiàn)方式,(《Effective Java》推薦)
public enum Singleton{
//定義一個(gè)枚舉的元素,它就是Singleton的一個(gè)實(shí)例
instance;
public void doSomething(){
}
}
使用方式如下:
Singleton singleton = Singleton.instance; singleton.doSomething();
5. 總結(jié)
本文主要對單例模式進(jìn)行了全面介紹,包括原理和實(shí)現(xiàn)方式,接下來我會繼續(xù)講解其他設(shè)計(jì)模式,有興趣可以繼續(xù)關(guān)注
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關(guān)文章
Java中實(shí)現(xiàn) SHA-256加密的兩種方式
這篇文章主要介紹了Java中實(shí)現(xiàn) SHA-256加密的兩種方式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01
IntelliJ IDEA中查看當(dāng)前類的所有繼承關(guān)系圖
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA中查看當(dāng)前類的所有繼承關(guān)系圖,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-10-10
Spring?Boot?整合RocketMq實(shí)現(xiàn)消息過濾功能
這篇文章主要介紹了Spring?Boot?整合RocketMq實(shí)現(xiàn)消息過濾,本文講解了RocketMQ實(shí)現(xiàn)消息過濾,針對不同的業(yè)務(wù)場景選擇合適的方案即可,需要的朋友可以參考下2022-06-06
JAVA8獲取list集合中重復(fù)的元素與獲取去重?cái)?shù)據(jù)實(shí)例
這篇文章主要給大家介紹了關(guān)于JAVA8獲取list集合中重復(fù)的元素與獲取去重?cái)?shù)據(jù)的相關(guān)資料,在實(shí)際開發(fā)中經(jīng)常會遇到需要找出(刪除)一個(gè)list中某些元素的屬性相同的元素,需要的朋友可以參考下2023-07-07
SpringBoot集成Shiro進(jìn)行權(quán)限控制和管理的示例
這篇文章主要介紹了SpringBoot集成Shiro進(jìn)行權(quán)限控制和管理的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03

