淺談23種設計模式之單例設計模式
單例模式
1、簡介
單例模式是設計模式中最簡單的形式之一。這一模式的目的是使得類的一個物件成為系統中的唯一例項。要實現這一點,可以從客戶端對其進行例項化開始。因此需要用一種只允許生成物件類的唯一例項的機制,“阻止”所有想要生成物件的訪問。使用工廠方法來限制例項化過程。這個方法應該是靜態方法(類方法),因為讓類的例項去生成另一個唯一例項毫無意義。
2、應用場景
舉一個例子,網站的計數器,一般也是採用單例模式實現,如果你存在多個計數器,每一個使用者的訪問都重新整理計數器的值,這樣的話你的實計數的值是難以同步的。但是如果採用單例模式實現就不會存在這樣的問題,而且還可以避免執行緒安全問題。同樣多執行緒的執行緒池的設計一般也是採用單例模式,這是由於執行緒池需要方便對池中的執行緒進行控制
同樣,對於一些應用程式的日誌應用,或者web開發中讀取配置檔案都適合使用單例模式,如HttpApplication 就是單例的典型應用。
3、優缺點
優點:
- 在記憶體中只有一個物件,節省記憶體空間;
- 避免頻繁的建立銷燬物件,可以提高效能;
- 避免對共享資源的多重佔用,簡化訪問;
- 為整個系統提供一個全域性訪問點。
缺點:
- 不適用於變化頻繁的物件;
- 濫用單例將帶來一些負面問題,如為了節省資源將資料庫連線池物件設計為的單例類,可能會導致共享連線池物件的程式過多而出現連線池溢位;
- 如果例項化的物件長時間不被利用,系統會認為該物件是垃圾而被回收,這可能會導致物件狀態的丟失;
4、實現方式
4.1 餓漢式
/** * 餓漢式 * 類載入到記憶體後,就例項化一個單例,JVM保證執行緒安全 * 簡單實用,推薦使用! * 唯一缺點:不管用到與否,類裝載時就完成例項化 */ public class Mgr01 { private static final Mgr01 INSTANCE = new Mgr01(); private Mgr01() {}; public static Mgr01 getInstance() { return INSTANCE; } public static void main(String[] args) { Mgr01 m1 = Mgr01.getInstance(); Mgr01 m2 = Mgr01.getInstance(); System.out.println(m1 == m2); } }
在開發中,推薦使用這種方式,簡單易用,如果追求更高的要求,可以參考以下懶漢式的實現方式。
4.2 懶漢式
/** * lazy loading * 也稱懶漢式 * 雖然達到了按需初始化的目的,但卻帶來執行緒不安全的問題 * 可以通過synchronized解決,但也帶來效率下降 */ public class Mgr06 { private static volatile Mgr06 INSTANCE; //JIT private Mgr06() { } public static Mgr06 getInstance() { if (INSTANCE == null) { //雙重檢查 synchronized (Mgr06.class) { if(INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr06(); } } } return INSTANCE; } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr06.getInstance().hashCode()); }).start(); } } }
雙重檢查,同時對部分程式碼進行鎖定,效率上有點影響,在看另外一種實現方式
/** * 靜態內部類方式 * JVM保證單例 * 載入外部類時不會載入內部類,這樣可以實現懶載入 */ public class Mgr07 { private Mgr07() { } private static class Mgr07Holder { private final static Mgr07 INSTANCE = new Mgr07(); } public static Mgr07 getInstance() { return Mgr07Holder.INSTANCE; } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr07.getInstance().hashCode()); }).start(); } } }
實際開發中,可以根據專案的業務場景進行選擇。
「其他文章」
- 模板化的封裝,降低業務程式碼開發
- 分享一個 SpringCloud Feign 中所埋藏的坑
- MySQL 事務常見面試題總結 | JavaGuide 稽核中
- 型別安全的 Go HTTP 請求
- 從幾次事故引起的對專案質量保障的思考
- 聯盟鏈 Hyperledger Fabric 應用場景
- 上半年最中意的 GitHub 更新「GitHub 熱點速覽 v.22.21」
- 為什麼我寫了路由懶載入但程式碼卻沒有分割?
- 這個設計原則,你認同嗎?
- SpringCloud基礎概念學習筆記(Eureka、Ribbon、Feign、Zuul)
- 自動微分原理
- layui資料表格搜尋
- Python 中的記憶體管理
- spring 配置檔案 --bean
- 【leetcode】239. 滑動視窗最大值
- Spring 原始碼(17)Spring Bean的建立過程(8)Bean的初始化
- SpringBoot進階教程(七十四)整合ELK
- 連結串列的基本操作和高頻演算法題
- 【python】python連線Oracle資料庫
- Python技法:浮點數取整、格式化和NaN處理