設計模式之單例設計模式

單例設計模式

  • 2.1 孤獨的太陽盤古開天,造日月星辰。
  • 2.2 餓漢造日
  • 2.3 懶漢的隊伍
  • 2.4 大道至簡

讀《秒懂設計模式總結》

單例模式(Singleton)是一種非常簡單且容易理解的設計模式。顧名思義,單例即單一的實例,確切地講就是指在某個系統中只存在一個實例,同時提供集中、統一的訪問接口,以使系統行為保持協調一致。singleton一詞在邏輯學中指“有且僅有一個元素的集合”,這非常恰當地概括了單例的概念,也就是“一個類僅有一個實例”。

2.1 孤獨的太陽盤古開天,造日月星辰。

從“夸父逐日”到“后羿射日”,太陽對于我們的先祖一直具有著神秘的色彩與非凡的意義。隨著科學的不斷發展,我們逐漸揭開了太陽系的神秘面紗。我們可以把太陽系看作一個龐大的系統,其中有各種各樣的對象存在,豐富多彩的實例造就了系統的美好。這個系統里的某些實例是唯一的,如我們賴以生存的恒星太陽。
在這里插入圖片描述
與其他行星或衛星不同的是,太陽是太陽系內唯一的恒星實例,它持續提供給地球充足的陽光與能量,離開它地球就不會有今天的勃勃生機,但倘若天上有9個太陽,那么將會帶來一場災難。太陽東升西落,循環往復,不多不少僅此一例。

2.2 餓漢造日

既然太陽系里只有一個太陽,我們就需要嚴格把控太陽實例化的過程。我們從最簡單的開始,先來寫一個Sun類。請參看代碼 。

public class Sun {}

太陽類Sun中目前什么都沒有。接下來我們得確保任何人都不能創建太陽的實例,否則一旦程序員調用代碼“new Sun()”,天空就會出現多個太陽,便又需要“后羿”去解決了。有些讀者可能會疑惑,我們并沒有寫構造器,為什么太陽還可以被實例化呢?這是因為Java可以自動為其加上一個無參構造器。為防止太陽實例泛濫將世界再次帶入災難,我們必須禁止外部調用構造器,請參看代碼

public class Sun {private void Sun(){} // 構造器私有化
}

我們在第3行將太陽類Sun的構造方法設為private,使其私有化,如此一來太陽類就被完全封閉了起來,實例化工作完全歸屬于內部事務,任何外部類都無權干預。既然如此,那么我們就讓它自己創建自己,并使其自有永有

public class Sun {private static final Sun sun = new Sun();public Sun() {} // private constructor  
}

代碼第3行中“private”關鍵字確保太陽實例的私有性、不可見性和不可訪問性;而“static”關鍵字確保太陽的靜態性,將太陽放入內存里的靜態區,在類加載的時候就初始化了,它與類同在,也就是說它是與類同時期且早于內存堆中的對象實例化的,該實例在內存中永生,內存垃圾收集器(Garbage Collector, GC)也不會對其進行回收;“final”關鍵字則確保這個太陽是常量、恒量,它是一顆終極的恒星,引用一旦被賦值就不能再修改;最后,“new”關鍵字初始化太陽類的靜態實例,并賦予靜態常量sun。這就是“餓漢模式”(eager initialization),即在初始階段就主動進行實例化,并時刻保持一種渴求的狀態,無論此單例是否有人使用。單例的太陽對象寫好了,可一切皆是私有的,外部怎樣才能訪問它呢?正如同程序入口的靜態方法main(),它不需要任何對象引用就能被訪問,我們同樣需要一個靜態方法getInstance()來獲取太陽的單例對象,同時將其設置為“public”以暴露給外部使用

public class Sun {private static final Sun sun = new Sun();public Sun() {} // private constructorpublic static Sun getInstance(){return sun;}
}

太陽單例類的雛形已經完成了,對外部來說只要調用Sun.getInstance()就可以得到太陽對象了,并且不管誰得到,或是得到幾次,得到的都是同一個太陽實例,這樣就確保了整個太陽系中恒星太陽的唯一合法性,他人無法偽造。當然,讀者還可以添加其他功能方法,如發光和發熱等,此處就不再贅述了。

2.3 懶漢的隊伍

至此,我們已經學會了單例模式的“餓漢模式”,讓太陽一開始就準備就緒,隨時供應免費日光。然而,如果始終沒人獲取日光,那豈不是白造了太陽,一塊內存區域被白白地浪費了?這正類似于商家貨品滯銷的情況,貨架上堆放著商品卻沒人買,白白浪費空間。因此,商家為了降低風險,規定有些商品必須提前預訂,這就是“懶漢模式”(lazy initialization)。沿著這個思路,我們繼續對太陽類進行改造

public class Sun {private static Sun sun = new Sun();public Sun() {} // private constructorpublic static Sun getInstance(){if (null == sun) {sun = new Sun(); //沒有sun才構造}return sun;}
}

可以看到我們一開始并沒有造太陽,所以去掉了關鍵字final,只有在某線程第一次調用第9行的getInstance()方法時才會運行對太陽進行實例化的邏輯代碼,之后再請求就直接返回此實例了。這樣的好處是如無請求就不實例化,節省了內存空間;而壞處是第一次請求的時候速度較之前的餓漢初始化模式慢,因為要消耗CPU資源去臨時造這個太陽(即使速度快到可以忽略不計)。這樣的程序邏輯看似沒問題,但其實在多線程模式下是有缺陷的。試想如果是并發請求的話,程序第10行的判空邏輯就會同時成立,這樣就會多次實例化太陽,并且對sun進行多次賦值(覆蓋)操作,這違背了單例的理念。我們再來改良一下,把請求方法加上synchronized(同步鎖)讓其同步,如此一來,某線程調用前必須獲取同步鎖,調用完后會釋放鎖給其他線程用,也就是給請求排隊,一個接一個按順序來

public class Sun {private static Sun sun = new Sun();public Sun() {} // private constructorpublic static synchronized Sun getInstance(){if (null == sun) {sun = new Sun(); //沒有sun才構造}return sun;}
}

我們將太陽類Sun中第9行的getInstance()改成了同步方法,如此可避免多線程陷阱。然而這樣的做法是要付出一定代價的,試想,線程還沒進入方法內部便不管三七二十一直接加鎖排隊,會造成線程阻塞,資源與時間被白白浪費。我們只是為了實例化一個單例對象而已,犯不上如此興師動眾,使用synchronized讓所有請求排隊等候。所以,要保證多線程并發下邏輯的正確性,同步鎖一定要加得恰到好處,其位置是關鍵所在

public class Sun {private volatile static Sun sun = new Sun();public Sun() {} // private constructorpublic static Sun getInstance(){if (null == sun) {synchronized (Sun.class) {if(null == sun){sun = new Sun(); //沒有sun才構造,只有第一次才構造 保證線程安全}}}return sun;}
}

我們在太陽類Sun中第3行對sun變量的定義不再使用find關鍵字,這意味著它不再是常量,而是需要后續賦值的變量;而關鍵字volatile對靜態變量的修飾則能保證變量值在各線程訪問時的同步性、唯一性。需要特別注意的是,對于第9行的getInstance()方法,我們去掉了方法上的關鍵字synchronized,使大家都可以同時進入方法并對其進行開發。請仔細閱讀每行代碼的注釋,有些人(線程)起早就是為了觀看日出,那么這些人會通過第10行的判空邏輯進入觀日臺。而在第11行我們又加上了同步塊以防止多個線程進入,這就類似于觀日臺是一個狹長的走廊,大家排隊進入。隨后在第12行我們又進行一次判空邏輯,這就意味著只有隊伍中的第一個人造了太陽,有幸看到了日出的第一縷陽光,而后面的人則統統離開,直到第17行得到已經造好的太陽

在這里插入圖片描述
隨后發生的事情我們就可以預見了,太陽高高升起,實例化操作完畢,起晚的人們都無須再進入觀日臺,直接獲取太陽實例就可以了,陽光普照大地,將溫暖灑向人間。大家注意到沒有,我們一共用了2個嵌套的判空邏輯,這就是懶加載模式的“雙檢鎖”:外層放寬入口,保證線程并發的高效性;內層加鎖同步,保證實例化的單次運行。如此里應外合,不僅達到了單例模式的效果,還完美地保證了構建過程的運行效率,一舉兩得。

2.4 大道至簡

相比“懶漢模式”,其實在大多數情況下我們通常會更多地使用“餓漢模式”,原因在于這個單例遲早是要被實例化占用內存的,延遲懶加載的意義并不大,加鎖解鎖反而是一種資源浪費,同步更是會降低CPU的利用率,使用不當的話反而會帶來不必要的風險。越簡單的包容性越強,而越復雜的反而越容易出錯。我們來看單例模式的類結構,如圖2-3所示。單例模式的角色定義如下。

在這里插入圖片描述
■ Singleton(單例):包含一個自己的類實例的屬性,并把構造方法用private關鍵字隱藏起來,對外只提供getInstance()方法以獲得這個單例對象。

除了“餓漢”與“懶漢”這2種單例模式,其實還有其他的實現方式。但萬變不離其宗,它們統統都是由這2種模式發展、衍生而來的。我們都知道Spring框架中的IoC容器很好地幫我們托管了業務對象,如此我們就不必再親自動手去實例化這些對象了,而在默認情況下我們使用的正是框架提供的“單例模式”。誠然,究其代碼實現當然不止如此簡單,但我們應該追本溯源,抓住其本質的部分,理解其核心的設計思想,再針對不同的應用場景做出相應的調整與變動,結合實踐舉一反三。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/40054.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/40054.shtml
英文地址,請注明出處:http://en.pswp.cn/news/40054.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【算法題】螺旋矩陣III (求解n階蛇形矩陣)

一、問題的提出 n階蛇形矩陣的特點是按照圖1所示的方式排列元素。n階蛇形矩陣是指矩陣的大小為nn,其中n為正整數。 題目背景 一個 n 行 n 列的螺旋矩陣可由如圖1所示的方法生成,觀察圖片,找出填數規律。填數規則為從 1 開始填到 nn。 圖1 …

【配置環境】Linux下安裝MySQL

目錄 一,環境 二,安裝步驟 1.使用包管理器安裝MySQL 2.配置MySQL的安全選項 3.設置root用戶使用密碼進行身份驗證(可選) 三,拓展知識 1.如何修改MySQL的密碼策略? 2.實現連接MySQL數據庫的測試代碼…

TiDB基礎介紹、應用場景及架構

1. 什么是newsql NewSQL 是對各種新的可擴展/高性能數據庫的簡稱,這類數據庫不僅具有NoSQL對海量數據的存儲管理能力,還保持了傳統數據庫支持ACID和SQL等特性。 NewSQL是指這樣一類新式的關系型數據庫管理系統,針對OLTP(讀-寫&…

經驗分享:企業數據倉庫建設方案總結!

導讀 在企業的數字化轉型浪潮中,數據被譽為“新時代的石油”,而數據倉庫作為數據管理與分析的核心基礎設施,在企業的信息化建設中扮演著重要的角色。本文將深入探討企業數據倉庫建設過程中所遇到的問題以及解決經驗,為正在籌備或…

進程/線程上下文切換會用掉你多少CPU?

進程是操作系統的偉大發明之一,對應用程序屏蔽了CPU調度、內存管理等硬件細節,而抽象出一個進程的概念,讓應用程序專心于實現自己的業務邏輯既可,而且在有限的CPU上可以“同時”進行許多個任務。但是它為用戶帶來方便的同時&#…

嵌入式Linux Qt5 (C++)開發欄目概述

本欄目開始介紹Linux系統下的Qt C程序開發,資源是以嵌入式為切入點(現在Linux系統下的Qt C程序開發好像就是應用于嵌入式),那就跟著一起學習Linux系統下的Qt C程序開發知識,再擴展一下嵌入式的知識吧。我這里默認已經熟…

php初解

php是什么? PHP,全稱 Hypertext Preprocessor ,中文翻譯“超文本預處理器”。 PHP是一種被廣泛應用的開源通用腳本語言,尤其適用于 Web 開發。 擁有快速,靈活,實用的特點,PHP能做任何事&#xf…

ORACLE中UNION、UNION ALL、MINUS、INTERSECT學習

1、UNION和UNION ALL的使用與區別 如果我們需要將兩個select語句的結果作為一個整體顯示出來,我們就需要用到union或者union all關鍵字。union的作用是將多個結果合并在一起顯示出來。 union和union all的區別是union會自動壓縮多個結果集合中的重復結果&#xff…

高速下載VisualGLM模型文件的解決方案

大家好,我是愛編程的喵喵。雙985碩士畢業,現擔任全棧工程師一職,熱衷于將數據思維應用到工作與生活中。從事機器學習以及相關的前后端開發工作。曾在阿里云、科大訊飛、CCF等比賽獲得多次Top名次。現為CSDN博客專家、人工智能領域優質創作者。喜歡通過博客創作的方式對所學的…

GO語言自底向上優化

Go Ballast(通過嘗試降低 GC 頻率以提高整體性能,針對所有 Go應用都適用) 首先我們明白GO語言GC觸發條件是由比例來觸發的。例如,當前存活內存10GB,觸發比例是100%,因此下次觸發GC的時候是當內存達到20GB的時候觸發GC。這種機制在…

碎片筆記|圖數據與圖神經網絡基礎介紹

前言:前段時間了解了一下圖神經網絡,本篇博客記錄一下相關知識,以備不時之需。 強烈推薦這篇博客(作者來自 Google Research),個人認為是圖神經網絡基礎入門的不二選擇! 目錄 一、圖數據1.1 定義…

Windows上使用FFmpeg實現本地視頻推送模擬海康協議rtsp視頻流

場景 Nginx搭建RTMP服務器FFmpeg實現海康威視攝像頭預覽: Nginx搭建RTMP服務器FFmpeg實現海康威視攝像頭預覽_nginx rtmp 海康攝像頭_霸道流氓氣質的博客-CSDN博客 上面記錄的是使用FFmpeg拉取海康協議攝像頭的rtsp流并推流到流媒體服務器。 如果在其它業務場景…

TCP/IP協議組

TCP/IP通信協議是目前最完整、使用最廣泛的通信協議。它的魅力在于可使不同硬件結構、不同操作系統的計算機相互通信。TCP/IP協議既可用于廣域網,也可用于局域網,它是Internet/Intranet的基石。TCP/IP通信協議事實上是一組協議。 TCP/IP協議可分為5層也可…

使用 Redis 實現共享 Session 的高效解決方案

系列文章目錄 文章目錄 系列文章目錄前言一、為什么需要共享 Session?二、使用 Redis 實現共享 Session1.安裝和配置 Redis2.實現 Session 存取操作3.使用 Session 數據三、測試共享 Session四、注意事項總結前言 在分布式系統中,實現共享 Session 是一個重要的問題。本文將…

GT Code - 圖譯算法編輯器(集成QT、C++、C、Linux、Git、java、web、go、高并發、服務器、分布式、網絡編程、云計算、大數據項目)

目錄 項目概述 發文意義 項目介紹 功能分析 設計概要 功能展示 項目文檔 項目概述 “GT Code 圖譯算法編輯器”是一款跨平臺、輕量級的代碼編輯器,主要面向軟件開發人員,它實現了編輯、編譯、繪制代碼流程圖、生成調試演示動畫等功能,以…

go版本glog/klog 參數使用方法心得

問題 glog很好用,但是官方文檔卻很爛,對于很多參數并沒有做詳細說明,于是通過看源碼測試,總結出以下使用方法 可選參數 flag.BoolVar(&logging.toStderr, "logtostderr", false, "log to standard error in…

空間分析專屬 Python 學習資料

空間數據分析能夠幫助我們更好地理解地理空間中的模式和關系,從而為決策提供支持。例如,城市規劃者可以使用空間數據分析來確定城市發展的最佳方向,環境科學家可以使用空間數據分析來評估污染的影響,而商業分析師可以使用空間數據…

react go實現用戶歷史登錄列表頁面

refer: http://ip-api.com/ 1.首先需要創建一個保存用戶歷史的登錄的表,然后連接go 2.在用戶登錄的時候,獲取用戶的IP IP位置,在后端直接處理數據即可(不需要在前端傳遞數據) (1)增加路由&am…

使用Java服務器實現UDP消息的發送和接收(多線程)

目錄 簡介:1. 導入必要的庫2. 創建服務器端代碼3. 創建客戶端代碼4. 實現多線程處理5. 測試運行示例代碼:函數說明服務器端代碼說明:客戶端代碼說明: 總結: 簡介: 在本篇博客中,我們將介紹如何…

genism word2vec方法

文章目錄 概述使用示例模型的保存與使用訓練參數詳解([原鏈接](https://blog.csdn.net/weixin_44852067/article/details/130221655))語料庫訓練 概述 word2vec是按句子來處理的Sentences(句子們) 使用示例 from gensim.models import Word2Vec #sent…