高併發下最全執行緒安全的單例模式幾種實現
為了節約系統資源,有時需要確保系統中某個類只有唯一一個例項,當這個唯一例項建立成功之後,我們無法再建立一個同類型的其他物件,所有的操作都只能基於這個唯一例項。 但是餓漢式單例類不能實現延遲載入,不管將來用不用始終佔據記憶體;懶漢式單例類執行緒安全控制煩瑣,而且效能受影響。可見,無論是餓漢式單例還是懶漢式單例都存在這樣那樣的問題,相比較內部靜態(static)內部類實現可以兼併以上問題。單例模式優缺點如下:
1.主要優點
單例模式的主要優點如下:
(1) 單例模式提供了對唯一例項的受控訪問。因為單例類封裝了它的唯一例項,所以它可以嚴格控制客戶怎樣以及何時訪問它。
(2)
(3) 允許可變數目的例項。基於單例模式我們可以進行擴充套件,使用與單例控制相似的方法來獲得指定個數的物件例項,既節省系統資源,又解決了單例單例物件共享過多有損效能的問題。
2.主要缺點
單例模式的主要缺點如下:
(1) 由於單例模式中沒有抽象層,因此單例類的擴充套件有很大的困難。
(2)
單例類的職責過重,在一定程度上違背了“單一職責原則”。因為單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業務方法,將產品的建立和產品的本身的功能融合到一起。
(3) 現在很多面向物件語言(如Java、C#)的執行環境都提供了自動垃圾回收的技術,因此,如果例項化的共享物件長時間不被利用,系統會認為它是垃圾,會自動銷燬並回收資源,下次利用時又將重新例項化,這將導致共享的單例物件狀態的丟失。
1 什麼是單例模式?
在文章開始之前我們還是有必要介紹一下什麼是單例模式。單例模式是為確保一個類只有一個例項,併為整個系統提供一個全域性訪問點的一種模式方法。
從概念中體現出了單例的一些特點:
(1)、在任何情況下,單例類永遠只有一個例項存在
(2)、單例需要有能力為整個系統提供這一唯一例項
為了便於讀者更好的理解這些概念,下面給出這麼一段內容敘述:
在計算機系統中,執行緒池、快取、日誌物件、對話方塊、印表機、顯示卡的驅動程式物件常被設計成單例。這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若干個印表機,但只能有一個Printer Spooler,以避免兩個列印作業同時輸出到印表機中。每臺計算機可以有若干通訊埠,系統應當集中管理這些通訊埠,以避免一個通訊埠同時被兩個請求同時呼叫。總之,選擇單例模式就是為了避免不一致狀態,避免政出多頭。
正是由於這個特點,單例物件通常作為程式中的存放配置資訊的載體,因為它能保證其他物件讀到一致的資訊。例如在某個伺服器程式中,該伺服器的配置資訊可能存放在資料庫或檔案中,這些配置資料由某個單例物件統一讀取,服務程序中的其他物件如果要獲取這些配置資訊,只需訪問該單例物件即可。這種方式極大地簡化了在複雜環境 下,尤其是多執行緒環境下的配置管理,但是隨著應用場景的不同,也可能帶來一些同步問題。
2 各式各樣的單例實現
溫馨提示:本文敘述中涉及到的相關原始碼可以在這裡進行下載原始碼,讀者可免積分下載。
1、餓漢式單例
餓漢式單例是指在方法呼叫前,例項就已經建立好了。下面是實現程式碼:
- package org.mlinge.s01;
- publicclass MySingleton {
- privatestatic MySingleton instance = new MySingleton();
- private MySingleton(){}
- publicstatic MySingleton getInstance() {
- return instance;
- }
- }
- package org.mlinge.s01;
- publicclass MyThread extends Thread{
- @Override
- publicvoid run() {
- System.out.println(MySingleton.getInstance().hashCode());
- }
- publicstaticvoid main(String[] args) {
- MyThread[] mts = new MyThread[10];
- for(int i = 0 ; i < mts.length ; i++){
- mts[i] = new MyThread();
- }
- for (int j = 0; j < mts.length; j++) {
- mts[j].start();
- }
- }
- }
以上程式碼執行結果:
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
從執行結果可以看出例項變數額hashCode值一致,這說明物件是同一個,餓漢式單例實現了。
2、懶漢式單例
懶漢式單例是指在方法呼叫獲取例項時才建立例項,因為相對餓漢式顯得“不急迫”,所以被叫做“懶漢模式”。下面是實現程式碼:
- package org.mlinge.s02;
- publicclass MySingleton {
- privatestatic MySingleton instance = null;
- private MySingleton(){}
- publicstatic MySingleton getInstance() {
- if(instance == null){//懶漢式
- instance = new MySingleton();
- }
- return instance;
- }
- }
- package org.mlinge.s02;
- publicclass MySingleton {
- privatestatic MySingleton instance = null;
- private MySingleton(){}
- publicstatic MySingleton getInstance() {
- try {
- if(instance != null){//懶漢式
- }else{
- //建立例項之前可能會有一些準備性的耗時工作
- Thread.sleep(300);
- instance = new MySingleton();
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return instance;
- }
- }
- package org.mlinge.s02;
- publicclass MyThread extends Thread{
- @Override
- publicvoid run() {
- System.out.println(MySingleton.getInstance().hashCode());
- }
- publicstaticvoid main(String[] args) {
- MyThread[] mts = new MyThread[10];
- for(int i = 0 ; i < mts.length ; i++){
- mts[i] = new MyThread();
- }
- for (int j = 0; j < mts.length; j++) {
- mts[j].start();
- }
- }
- }
執行結果如下:
- 1210420568
- 1210420568
- 1935123450
- 1718900954
- 1481297610
- 1863264879
- 369539795
- 1210420568
- 1210420568
- 602269801
從這裡執行結果可以看出,單例的執行緒安全性並沒有得到保證,那要怎麼解決呢?
3、執行緒安全的懶漢式單例
要保證執行緒安全,我們就得需要使用同步鎖機制,下面就來看看我們如何一步步的解決 存線上程安全問題的懶漢式單例(錯誤的單例)。
(1)、 方法中宣告synchronized關鍵字
出現非執行緒安全問題,是由於多個執行緒可以同時進入getInstance()方法,那麼只需要對該方法進行synchronized的鎖同步即可:
- package org.mlinge.s03;
- publicclass MySingleton {
- privatestatic MySingleton instance = null;
- private MySingleton(){}
- publicsynchronizedstatic MySingleton getInstance() {
- try {
- if(instance != null){//懶漢式
- }else{
- //建立例項之前可能會有一些準備性的耗時工作
- Thread.sleep(300);
- instance = new MySingleton();
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return instance;
- }
- }
此時任然使用前面驗證多執行緒下執行情況的MyThread類來進行驗證,將其放入到org.mlinge.s03包下執行,執行結果如下:
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
從執行結果上來看,問題已經解決了,但是這種實現方式的執行效率會很低。同步方法效率低,那我們考慮使用同步程式碼塊來實現:
(2)、 同步程式碼塊實現
- package org.mlinge.s03;
- publicclass MySingleton {
- privatestatic MySingleton instance = null;
- private MySingleton(){}
- //public synchronized static MySingleton getInstance() {
- publicstatic MySingleton getInstance() {
- try {
- synchronized (MySingleton.class) {
- if(instance != null){//懶漢式
- }else{
- //建立例項之前可能會有一些準備性的耗時工作
- Thread.sleep(300);
- instance = new MySingleton();
- }
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return instance;
- }
- }
(3)、 針對某些重要的程式碼來進行單獨的同步(可能非執行緒安全)
針對某些重要的程式碼進行單獨的同步,而不是全部進行同步,可以極大的提高執行效率,我們來看一下:
- package org.mlinge.s04;
- publicclass MySingleton {
- privatestatic MySingleton instance = null;
- private MySingleton(){}
- publicstatic MySingleton getInstance() {
- try {
- if(instance != null){//懶漢式
- }else{
- //建立例項之前可能會有一些準備性的耗時工作
- Thread.sleep(300);
- synchronized (MySingleton.class) {
- instance = new MySingleton();
- }
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return instance;
- }
- }
- 1481297610
- 397630378
- 1863264879
- 1210420568
- 1935123450
- 369539795
- 590202901
- 1718900954
- 1689058373
- 602269801
(4)、 Double Check Locking 雙檢查鎖機制(推薦)
為了達到執行緒安全,又能提高程式碼執行效率,我們這裡可以採用DCL的雙檢查鎖機制來完成,程式碼實現如下:
- package org.mlinge.s05;
- publicclass MySingleton {
- //使用volatile關鍵字保其可見性
- volatileprivatestatic MySingleton instance = null;
- private MySingleton(){}
- publicstatic MySingleton getInstance() {
- try {
- if(instance != null){//懶漢式
- }else{
- //建立例項之前可能會有一些準備性的耗時工作
- Thread.sleep(300);
- synchronized (MySingleton.class) {
- if(instance == null){//二次檢查
- instance = new MySingleton();
- }
- }
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return instance;
- }
- }
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
這裡在宣告變數時使用了volatile關鍵字來保證其執行緒間的可見性;在同步程式碼塊中使用二次檢查,以保證其不被重複例項化。集合其二者,這種實現方式既保證了其高效性,也保證了其執行緒安全性。
4、使用靜態內建類實現單例模式
DCL解決了多執行緒併發下的執行緒安全問題,其實使用其他方式也可以達到同樣的效果,程式碼實現如下:
- package org.mlinge.s06;
- publicclass MySingleton {
- //內部類
- privatestaticclass MySingletonHandler{
- privatestatic MySingleton instance = new MySingleton();
- }
- private MySingleton(){}
- publicstatic MySingleton getInstance() {
- return MySingletonHandler.instance;
- }
- }
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
5、序列化與反序列化的單例模式實現
靜態內部類雖然保證了單例在多執行緒併發下的執行緒安全性,但是在遇到序列化物件時,預設的方式執行得到的結果就是多例的。
程式碼實現如下:
- package org.mlinge.s07;
- import java.io.Serializable;
- publicclass MySingleton implements Serializable {
- privatestaticfinallong serialVersionUID = 1L;
- //內部類
- privatestaticclass MySingletonHandler{
- privatestatic MySingleton instance = new MySingleton();
- }
- private MySingleton(){}
- publicstatic MySingleton getInstance() {
- return MySingletonHandler.instance;
- }
- }
- package org.mlinge.s07;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- publicclass SaveAndReadForSingleton {
- publicstaticvoid main(String[] args) {
- MySingleton singleton = MySingleton.getInstance();
- File file = new File("MySingleton.txt");
- try {
- FileOutputStream fos = new FileOutputStream(file);
- ObjectOutputStream oos = new ObjectOutputStream(fos);
- oos.writeObject(singleton);
- fos.close();
- oos.close();
- System.out.println(singleton.hashCode());
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- try {
- FileInputStream fis = new FileInputStream(file);
- ObjectInputStream ois = new ObjectInputStream(fis);
- MySingleton rSingleton = (MySingleton) ois.readObject();
- fis.close();
- ois.close();
- System.out.println(rSingleton.hashCode());
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
- 865113938
- 1442407170
解決辦法就是在反序列化的過程中使用readResolve()方法,單例實現的程式碼如下:
- package org.mlinge.s07;
- import java.io.ObjectStreamException;
- import java.io.Serializable;
- publicclass MySingleton implements Serializable {
- privatestaticfinallong serialVersionUID = 1L;
- //內部類
- privatestaticclass MySingletonHandler{
- privatestatic MySingleton instance = new MySingleton();
- }
- private MySingleton(){}
- publicstatic MySingleton getInstance() {
- return MySingletonHandler.instance;
- }
- //該方法在反序列化時會被呼叫,該方法不是介面定義的方法,有點兒約定俗成的感覺
- protected Object readResolve() throws ObjectStreamException {
- System.out.println("呼叫了readResolve方法!");
- return MySingletonHandler.instance;
- }
- }
- 865113938
- 呼叫了readResolve方法!
- 865113938
6、使用static程式碼塊實現單例
靜態程式碼塊中的程式碼在使用類的時候就已經執行了,所以可以應用靜態程式碼塊的這個特性的實現單例設計模式。
- package org.mlinge.s08;
- publicclass MySingleton{
- privatestatic MySingleton instance = null;
- private MySingleton(){}
- static{
- instance = new MySingleton();
- }
- publicstatic MySingleton getInstance() {
- return instance;
- }
- }
- package org.mlinge.s08;
- publicclass MyThread extends Thread{
- @Override
- publicvoid run() {
- for (int i = 0; i < 5; i++) {
- System.out.println(MySingleton.getInstance().hashCode());
- }
- }
- publicstaticvoid main(String[] args) {
- MyThread[] mts = new MyThread[3];
- for(int i = 0 ; i < mts.length ; i++){
- mts[i] = new MyThread();
- }
- for (int j = 0; j < mts.length; j++) {
- mts[j].start();
- }
- }
- }
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
7、使用列舉資料型別實現單例模式
列舉enum和靜態程式碼塊的特性相似,在使用列舉時,構造方法會被自動呼叫,利用這一特性也可以實現單例:
- package org.mlinge.s09;
- publicenum EnumFactory{
- singletonFactory;
- private MySingleton instance;
- private EnumFactory(){//列舉類的構造方法在類載入是被例項化
- instance = new MySingleton();
- }
- public MySingleton getInstance(){
- return instance;
- }
- }
- class MySingleton{//需要獲實現單例的類,比如資料庫連線Connection
- public MySingleton(){}
- }
- package org.mlinge.s09;
- publicclass MyThread extends Thread{
- @Override
- publicvoid run() {
-
相關推薦
高併發下最全執行緒安全的單例模式幾種實現
為了節約系統資源,有時需要確保系統中某個類只有唯一一個例項,當這個唯一例項建立成功之後,我們無法再建立一個同類型的其他物件,所有的操作都只能基於這個唯一例項。 但是餓漢式單例類不能實現延遲載入,不管將來用不用始終佔據記憶體;懶漢式單例類執行緒安全控制煩瑣,而且效能受影響。可
[C++][執行緒安全]單例模式下雙檢查鎖和執行緒
問題 在設計模式中,有一個很經典的模式-單例模式,它可能是實現上最簡單的模式,在程式碼中也經常使用,在單執行緒下,毫無疑問延遲化載入是比較常用的,但是在多執行緒條件下,單例模式的延遲載入可能就會出現一些問題。 如以下的程式碼: T* GetInstance(
C++的單例模式與執行緒安全單例模式(懶漢/餓漢)
單例模式 單例模式:是一種常用的軟體設計模式。在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中一個類只有一個例項。即一個類只有一個物件例項。 實現簡單的單例模式:建構函式宣告為private或protect防止被外部函式
高併發第三彈:執行緒安全-原子性
執行緒安全性? 執行緒安全性主要體現在三個方面:原子性、可見性、有序性 原子性:提供了互斥訪問,同一時刻只能有一個執行緒來對它進行操作 可見性:一個執行緒對主記憶體的修改可以及時的被其他執行緒觀察到 有序性:一個執行緒觀察其他執行緒中的指令執行順序,由於指令重排序的存在,該觀察結果一般雜亂無序。 本章主
Java併發程式設計(3)-構造執行緒安全類的模式
文章目錄 一、例項限制模式 1.1、 限制變數確保執行緒安全 1.2、分析ArrayList的執行緒安全性 1.3、總結 二、委託執行緒安全模式 2.
執行緒安全單例設計模式+序列化
懶漢式單例模式會引來執行緒安全問題,即多執行緒時可能會建立多個例項,而且在反序列化時也可能會建立新的例項。看了大大們的實現方式,覺得以下一種比較簡單方便理解。 一、下面是會造成執行緒安全的餓漢單例模式。用四列印下會發現hashcode不一致 public class Singleton {
高效能的執行緒安全單例——Java基礎語法系列
大家都知道,一般實現單例有兩種寫法: 餓漢式 和 懶漢式, 餓漢式是執行緒安全的,在編譯期間已完成初始化,載入到了記憶體裡。 懶漢式一般寫法是非執行緒安全的, 那懶漢式的執行緒安全單例應該如何實現呢,以及如何寫出低耗能的執行緒安全單例呢 ? 單例實現關鍵點 建構函式私有,
Qt下實現支援多執行緒的單例模式
1. 程式碼介紹 實現單例模式的程式碼很多。 本文的單例模式實現程式碼是本人一直在工程專案中使用的,現拿出和大家交流分享。 本文實現的單例模式,支援多執行緒,採用雙重校驗檢索的方式,整合析構類,杜絕記憶體洩漏,穩定性好。 使用C++/Qt的朋友們可以瞭解一下。 不再廢話,直接上程式碼。
java通過雙重檢測或列舉類實現執行緒安全單例(懶漢模式)
雙重檢測實現 /** * 懶漢模式->雙重同步鎖單例模式 */ public class SingletonExample5 { private SingletonExample5() { } //volatile + 雙重檢測機制 -> 禁止指令重排序
執行緒安全(單例與多例)
又週五了,時間過得好快,住在集體宿舍,幾個宅男共處一室好是無聊,習慣性來到CSDN。今天一個應屆生同事突然問我為什麼老大要求我們不要在Service裡寫成員變數,說不安全,說為什麼不安全讓他自己去了解,看上去他沒有找到頭緒很是痛苦,想想當初這個問題也困擾過自己,向
執行緒安全的生產者消費者四種實現方法
問題描述 在IT技術面試過程中,我們經常會遇到生產者消費者問題(Producer-consumer problem), 這是多執行緒併發協作問題的經典案例。場景中包含三個物件,生產者(Producer),消費者(Consumer)以及一個固定大小的緩衝區(Buffer)。生產者的主要作用是不斷生成資料放到緩衝
執行緒涉及單例模式
/** * 單例模式涉及的兩個問題 * * @author 羅摩銜那 * */ /* * 惡漢式 * 優點:當類被載入的時候,已經建立好了一個靜態的物件,因此,是執行緒安全的 * 缺點:這個物件還沒有被使用就被創建出來了 */ class Single { private s
日常小結-多執行緒的單例模式的三種實現方式
多執行緒單例模式在很多併發的書裡面都有寫。這裡做一個簡單的總結。主要內容來自《java併發程式設計的藝術》《java多執行緒程式設計核心》 單例模式的分類 餓漢模式:類初始化的時候就進行建立單例模式 懶漢模式:在呼叫getinstance方法的時候才建
JAVA_多執行緒_單例模式
這篇是入職之後的第二篇了,上一篇我簡單介紹了一下LOCK裡面的類的方法,感興趣的話可以去了解一下,以後堅持每週至少會更新一篇關於多執行緒方面的文章,希望博友們可以一起加油成長。 這篇主要的內容是單例模式在多執行緒環境下的設計,這篇算是比較重要的內容,我會進行文字和程式碼的共同說明來講解記錄 1、立即載入(餓
【Java多執行緒】單例模式與多執行緒
單例模式大家都不陌生,即讓一個類只有一個例項。 單例模式分為懶漢式和餓漢式。 懶漢式☞方法呼叫時再例項化物件,什麼時候用什麼時候例項化,比較懶。 餓漢式☞方法呼叫前物件就已經建立好了,比較有捉急。 本文著重描述懶漢式與多執行緒的內容。 1.餓漢式 public
Java多執行緒之單例模式-yellowcong
我們常見的單利模式有餓漢式和懶漢式,懶漢式,就是在用的時候,再例項化物件,餓漢式,是還沒有開飯,就已經把物件例項化好了。在多執行緒開發中,解決單例的時候,有兩種解決方案,1、(懶漢式)同步程式碼塊
馬士兵老師高併發之6大執行緒池
Executor 執行器,這是一個介面,內部維護了一個方法execute它負責執行一項任務。引數為Runnable,方法的具體實現由我們自己來執行。如下面的程式碼,我們既可以使用單純的方法呼叫也可以新啟一個新的執行緒去執行Runnable的run方法。 import ja
高併發第六彈:執行緒封閉
當訪問共享的可變資料時,通常需要使用同步。一種避免使用同步的方式就是不共享資料。如果僅在單執行緒內訪問資料,就不需要同步。這種技術被稱為執行緒封閉。 它其實就是把物件封裝到一個執行緒裡,只有一個執行緒能看到這個物件,那麼這個物件就算不是執行緒安全的,也不會出現任何執行緒安全方面的問題。 二 執行緒封閉技術有
java高併發學習(九)-----多執行緒的團隊協作:同步控制
Java高併發學習(八)-------多執行緒的團隊協作:同步控制 同步控制是併發程式必不可少的重要手段。之前介紹的synchronized關鍵字就是一種最簡單的控制方法。同時,wait()和notify()方法起到了執行緒等待和通知的作用。這些工具對於實現複雜的多
java併發之如何解決執行緒安全問題
併發(concurrency)一個並不陌生的詞,簡單來說,就是cpu在同一時刻執行多個任務。 而Java併發則由多執行緒實現的。 在jvm的世界裡,執行緒就像不相干的平行空間,序列在虛擬機器中。(當然這是比較籠統的說法,執行緒之間是可以互動的,他們也不一定是序列。) 多執行緒的存在就是壓榨cpu,提高程