單例模式(Singleton)看了就懂

来源:http://www.cnblogs.com/TheOldQi/archive/2017/09/19/7553293.html
-Advertisement-
Play Games

單例,故名思議,一個只能創建一個實例的類。 單例被廣泛應用於Spring的bean(預設)、線程池、資料庫連接池、緩存,還有其他一些無狀態的類如servlet。 一個沒必要多例的類實現了單例可以節約空間(顯而易見),節省資源(線程、資料庫連接)。 單例模式有這麼多好處,那我們來實現它吧,首先想到的是 ...


單例,故名思議,一個只能創建一個實例的類。

單例被廣泛應用於Spring的bean(預設)、線程池、資料庫連接池、緩存,還有其他一些無狀態的類如servlet。

一個沒必要多例的類實現了單例可以節約空間(顯而易見),節省資源(線程、資料庫連接)。

單例模式有這麼多好處,那我們來實現它吧,首先想到的是創建一個對象要使用new方法,new方法調用的是類的構造函數,想要不被程式員隨意的new對象可以將類的構造函數設為私有,然後再提供一個獲取這個類實例的方法,所以就有了下麵這個實現。

1、只能正確運行在單線程模式下的單例實現:

 1 public class SingletonSingleThread {
 2 
 3     private static SingletonSingleThread instance;
 4     
 5     private SingletonSingleThread(){}
 6     
 7     public static SingletonSingleThread getInstance(){
 8         if(instance == null){
 9             instance = new SingletonSingleThread();
10         }
11         return instance;
12     }
13 }

以上代碼只能保證在單線程下運行,在多線程環境下可能有多個線程在第8行時判斷到instance當前是指向null,然後都去執行第9行的代碼。

如果讀者瞭解過volatile修飾變數的作用,可能想到將以上代碼修改成如下形式,因為volatile可以保證線程之間變數的“可見性”,就可以保證每個線程在第8行判斷的時候都是instance的最新引用了?

 1  public class SingletonSingleThread {
 2  
 3      private static volatile SingletonSingleThread instance;
 4      
 5      private SingletonSingleThread(){}
 6       
 7      public static SingletonSingleThread getInstance(){
 8          if(instance == null){
 9              instance = new SingletonSingleThread();
10         }
11         return instance;
12     }
13 }

但是第8行和第9行是兩行代碼,並不是原子操作,完全可能出現線程A執行通過第8行校驗,準備執行第9行的時候,另一個線程B來到第8行校驗,也通過了校驗。

實際上就算是代碼中的一行指令也不是原子操作,在編譯成.class文件後未必是一行位元組碼,就算是一行位元組碼,在解釋執行或即時編譯執行轉化成機器碼時也可能對應多條指令,以上結論原理不在本文介紹範圍之內。

思考:java里有一個很方便的實現線程安全的synchronized修飾符,加上不就實現線程安全了嗎?是的,下麵這個實現就是在getInstance方法上簡單加上synchronized修飾符。

2、對性能不敏感的多線程安全單例實現:

 1 public class SingletonSlow {
 2 
 3     private static SingletonSlow instance;
 4     
 5     private SingletonSlow(){}
 6     
 7     public synchronized static SingletonSlow getInstance(){
 8         if(instance == null){
 9             instance = new SingletonSlow();
10         }
11         return instance;
12     }
13 }

如果程式對一個單例實現的getInstance()方法效率不敏感可以使用這種實現方式。好處就是直觀,簡單。壞處也顯而易見,只有當SingletonSlow對象第一次被創建時是需要同步的,之後的調用synchronized都將是額外的負擔。

 

思考:能不能只在第一次調用對象實例需要創建的時候才同步代碼,其他時候不同步代碼的方法呢?有的。

3、DCL單例模式(雙重鎖檢查) 註意:這種方式只能應用於JDK1.5及以後的版本,JDK1.5解決了volatile無法實現雙重鎖單例的bug

 1 public class SingletonDCL {
 2 
 3     private volatile static SingletonDCL instance;
 4 
 5     private SingletonDCL(){}
 6     
 7     public static SingletonDCL getInstance(){
 8         if(instance == null){
 9             synchronized(SingletonDCL.class){
10                 if(instance == null){
11                     instance = new SingletonDCL();
12                 }
13             }
14         }
15         return instance;
16     }
17 }

DCL方式雖然也用到了同步保證單例,但是它的效率要遠遠高於第2種實現方式。首先實例變數instance用volatile修飾,保證第8行拿到的是instance的最新引用,這行判斷可以快速逃避instance已經被初始化的情況,當然這行代碼還是會出現多個線程都判斷為真的情況,第9行的代碼保證了10-12行代碼只有一個線程能執行,第10行重新判斷instance引用為空的意義在於解決以下情況的發生:

  ①若線程A和B同時通過第8行代碼判斷,等待進入同步塊依次執行,若A先執行,則B獲得執行時間時instance已經被線程A初始化了。

  ②若線程A通過第8行代碼判斷,進入同步塊開始執行11行代碼未執行完時,線程B執行到第8行判斷通過,這時A同步代碼塊執行完畢,B雖然接下來不需要等待直接進入同步塊,但instance也是已經被初始化過的。

思考:這個寫法看上去很難理解,我想到一個實現方式,既然是靜態變數類型,直接在變數聲明時賦值,或者寫一個靜態塊初始化不就好了,靜態類型的變數初始化是伴隨著類載入進行的,不也是線程安全的嗎?確實。

4、利用類載入器實現的急切載入單例:

 1 public class SingletonEagerly {
 2 
 3     private static SingletonEagerly instance = new SingletonEagerly();
 4     
 5     private SingletonEagerly(){}
 6     
 7     public static SingletonEagerly getInstance(){
 8         return instance;
 9     }
10 }

為什麼叫急切載入單例呢,因為依賴於java的類載入機制,類被載入的時機未必是在需要使用類的對象時,也許在還不需要這個類的實例的時候類的實例就已經被初始化了,如果這個單例很耗費資源,我們肯定想採用懶載入的方式去實現他。

這種實現還有一個缺陷就是依賴於當前線程的ClassLoader(),如果類被多個ClassLoader載入(比如servlet),那就無法保證單例了。

思考:怎樣避免類的實例只有在真正需要它的時候才被初始化呢? 可以用私有靜態內部類實現。

5、利用類載入器實現的懶載入單例:

 1 public class SingletonLazyByClassLoader {
 2 
 3     private SingletonLazyByClassLoader(){}
 4     
 5     public static SingletonLazyByClassLoader getInstance(){
 6         return SingletonHolder.instance;
 7     }
 8     
 9     private static class SingletonHolder{
10         private static final SingletonLazyByClassLoader  instance= new SingletonLazyByClassLoader();
11     }
12 }

將類的唯一實例放在內部靜態類里,這樣外部只要不調用getInstance方法就不會有類載入器載入去載入內部靜態類,而內部靜態類是私有的,所以只有第一次instance方法被調用的時候才會初始化了類的實例變數。

遺憾的是這種實現方式仍然無法避免ClassLoader不一樣的問題。

 


如果實在對getInstance效率無要求,使用方式2實現單例最簡單直觀;

如果程式運行在Jdk1.5及以上的環境,又不會覺得實現麻煩,強烈推薦使用方式3實現單例,高效、可靠;

如果嫌方式3麻煩或者運行在Jdk1.5以前的版本,則根據是否對懶載入有需求使用方式5或方式4,同時要保證程式中用到的類載入器是AppClassLoader或先代載入器是AppClassLoader,否則單例會失效。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • package com.swift; //這裡導入的包是java.sql.Connection而不是com.mysql.jdbc.Connection import java.sql.Connection; import java.sql.DriverManager; import java.sql ...
  • 4.三大範式: ...
  • ptyhon版本 Python 3.5.4 支持中文直接輸入和顯示,ptyhon2.x 中文支持需要轉碼 編輯器:pycharm hello world 註釋 單行註釋:用#作為單行註釋 多行註釋:使用3個單引號(''')或者3個雙引號(""")圍起來的內容將被註釋。如下 變數(var) 變數命名規 ...
  • 題目描述 形如2P-1的素數稱為麥森數,這時P一定也是個素數。但反過來不一定,即如果P是個素數,2P-1不一定也是素數。到1998年底,人們已找到了37個麥森數。最大的一個是P=3021377,它有909526位。麥森數有許多重要應用,它與完全數密切相關。 任務:從文件中輸入P(1000<P<310 ...
  • 上次我們講了Java中的一些基本的語法;今天我們就講一點內容,來說說Java中的方法和方法重載以及需要註意的一些地方; 方法: Java的方法類似與其他語言的函數,是一段用來完成特定功能的代碼片段, 聲明格式: [修飾符1 修飾符2 ....] ,返回值類型 方法名 (形式參數列表) { Java語 ...
  • 要理解select.select模塊其實主要就是要理解它的參數, 以及其三個返回值。select()方法接收並監控3個通信列表, 第一個是所有的輸入的data,就是指外部發過來的數據,第2個是監控和接收所有要發出去的data(outgoing data),第3個監控錯誤信息在網上一直在找這個sele ...
  • 由於最近新上的項目很多模塊沒有做數據緩存,大量的請求都會到資料庫去查詢,為了減輕資料庫的壓力以及提高網站響應速度,所以在這裡採用了spring 提供的註解+redis實現對數據的緩存,主要針對非熱點數據,例如 省市,銀行卡列表等做緩存,在這裡主要是查詢做一個緩存實例。 pom.xml (加入spri ...
  • 序 好久沒寫設計模式了,自從寫了兩篇之後,就放棄治療了,主要還是工作太忙了啊(藉口,都是藉口),過完年以後一直填坑,填了好幾個月,總算是穩定下來了,可以打打醬油了。 為什麼又重新開始寫設計模式呢?學習使我快樂啊(我裝逼起來我自己都害怕),其實主要是最近填坑的時候看源代碼有點暈,很多代碼不知道他們為什 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...