多線程案例-單例模式

單例模式

設計模式的概念

設計模式好比象棋中的"棋譜".紅方當頭炮,黑方馬來跳.針對紅方的一些走法,黑方應招的時候有一些固定的套路.按照套路來走局勢就不會吃虧.

軟件開發中也有很多常見的"問題場景".針對這些問題的場景,大佬們總結出了一些固定的套路.按照這些套路來實現代碼,也不會吃虧

單例模式概念

單例 = 單個實例(對象)

具體來說,就是某個類,在一個進程中,只應該創建出一個實例.(也就是原則上不應該有多個)

使用單例模式,就可對代碼進行更嚴格的校驗與檢查.

期望讓機器(編譯器)能夠對代碼中指定的類,創建的實例個數,進行校驗.如果發現創建多個實例了,就直接讓編譯器報錯這種~~

這一點在很多場景上都需要,一般就是一個對象持有(管理)大量數據時,比如JDBC中的DataSource實例只需要一個.

單例模式具體的實現方式有很多.最常見的是"餓漢"和"懶漢"兩種.

餓漢模式

類加載的同時,創建實例.

也就是說實例在類加載的時候就創建了,創建時機非常早,相當于程序一啟動,實例就創建了.

class Singleton {private static Singleton instance = new Singleton();private Singleton(){}public static Singleton getInstance() {return instance;}
}public class TestSingleton {public static void main(String[] args) {Singleton.getInstance();Singleton s = new Singleton();}
}

1.instance是Singleton類對象里持有的屬性.類對象是指Singleton.class(就是從.class加載至內存中,表示類的一個數據結構).

2.private Singleton() {} 是在設置私有構造方法,保證其它代碼不能創建出新的對象.

比如:Singleton s = new Singleton();在這里就無法執行

3.其它代碼如果想要獲得這個類的唯一實例,就可以通過getInstance()方法獲取.

對于餓漢來說,getInstance直接返回Instance實例,這個操作本質上是"讀操作",多個線程讀取同一個變量,是線程安全的.?

懶漢模式-單線程版

類加載的時候不創建實例.第一次使用的時候才創建實例.

class Singleton {private static Singleton instance = null;//這個引用先初始化為null,而不是立即創建實例.private Singleton() {}public static Singleton getInstance() {if(instance == null) {instance = new Singleton();}return instance;}
}

在這個代碼中,首次調用getInstance時,instance引用為null.進入里面的if條件,把實例創建出來.如果后續再次調用,if就不進入.而是直接返回之前創建的引用了.

這樣設定,仍可以保證該類的實例是唯一一個.于此同時,創建實例的時機就不是程序驅動的了,而是第一次調用getInstance時(操作執行時機看程序具體需求.大概率要比餓漢這種方式要晚一些,甚至有可能整個程序壓根用不到這個方法,也就把創建的操作給省下了).?

注意:懶漢模式是比餓漢模式更好一些的.

在計算機中,懶的思想非常有意義:

比如有一個非常大的文件(10GB).有一個編輯器,使用編輯器打開這個文件.

如果是按照"餓漢模式",編輯器就會先把這10GB的數據加載到內存中,然后再進行統一的展示.(即使加載了這么多數據,用戶還得一點一點看,沒法一下子看完這么多..)

如果是按照"懶漢模式",編輯器就會只讀取一小部分數據(比如只讀10KB),把這10KB先展示出來.隨著用戶進行翻頁之類的操作,再繼續讀后續的數據.

懶漢模式-多線程版

?上面的懶漢模式是線程不安全的.

線程安全發生在首次創建實例時.如果在多個線程中同時調用getInstance方法,就可能導致創建出多個實例.

一旦實例已經創建好了,后面再多線程環境調用getInstance就不再有線程安全問題了(不再修改Instance了).

舉個例子:

譬如這種情況,兩次的if條件都符合,會創建兩個實例,顯然不符合規定.

而這時就很容易想到使用synchronized來解決這個問題.

class Singleton {private static Object locker = new Object();private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {synchronized(locker) {if(instance == null) {instance = new Singleton();}}return instance;}
}

這樣寫確實可以解決線程安全的問題.但還是有一個問題:

比如Instance已經創建過了.此時后續再調用getInstance就都是返回Instance實例了吧(于是此處的操作就是純粹的讀操作了,也就不會有線程安全問題了).

此時,針對這個已經沒有線程安全問題的代碼,仍然時每次調用都先加鎖再解鎖,此時效率就非常低了!!!(加鎖意味著會產生阻塞,一旦線程阻塞,啥時候能解除,就不知道了.你可以認為:只要一個代碼里加鎖了,基本注定就要和"高性能"無緣).?

因此我們說,在不該加鎖的時候是不能亂加的.

解決方案:可以在加鎖外面再套一層if,以判斷是否加鎖.(如果instance為null,說明是首次調用,首次調用就需要考慮線程安全問題->要加鎖 / 如果非null,說明是后續調用->不必加鎖)

再來看一下修改的代碼:

?
class Singleton {private static Object locker = new Object();private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {//第一個if判定的是是否加鎖(保證執行效率)synchronized(locker) {if(instance == null) {//第二個if判定的是是否要創建對象(保障線程安全)instance = new Singleton();}}}return instance;}
}?

但是又雙有一個問題,就是指令重排序引起的線程安全問題.

我們知道,指令重排序,也是編譯器優化的一種方式.(調整原有代碼的執行順序,保證邏輯不變的前提下,提高程序的效率).

這里指的就是instance = new Singleton();?

這條語句可以拆分成多個指令:(1)申請一段內存空間 (2)在內存上調用構造方法,創建出這個實例 (3)把這個內存地址賦給Instance引用變量

正常情況下:是按照(1)(2)(3)順序執行的,但編譯器也可優化成(1)(3)(2)執行,多線程指令重排序可能有問題.

原因如下:

解決方案:給instance加上volatile(volatile可以防止指令重排序).

加上之后,針對這個變量的讀寫操作,就不會出現指令重排序了.

最后代碼如下:

?

class Singleton {private static Object locker = new Object();private static volatile Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {//第一個if判定的是是否加鎖(保證執行效率)synchronized(locker) {if(instance == null) {//第二個if判定的是是否要創建對象(保障線程安全)instance = new Singleton();}}}return instance;}
}

?

?

?

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

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

相關文章

vue實現可拖拽列表

直接上代碼 <!-- vue實現可拖拽列表 --> <template><div><button click"logcolig">打印數據</button><TransitionGroup name"list" tag"div" class"container"><divclass"item"v-f…

常見請求頭與響應頭你了解哪些?

常見的 HTTP 請求頭和響應頭包括&#xff1a; 常見的請求頭&#xff1a; User-Agent&#xff1a;標識客戶端代理信息&#xff0c;通常用于識別用戶使用的瀏覽器或設備類型。 Accept&#xff1a;指示客戶端可以接受的內容類型&#xff0c;例如 text/html, application/json 等…

深度學習記錄--激活函數

激活函數的種類 對于激活函數的選擇&#xff0c;通常有以下幾種 sigmoid&#xff0c;tanh&#xff0c;ReLU&#xff0c;leaky ReLU 激活函數的選擇 之前logistic回歸一直使用的激活函數都是sigmoid函數&#xff0c;但一般來說&#xff0c;tanh函數是比sigmoid函數更加好的選…

【Python】 生成二維碼

創建了一個使用 python 創建二維碼的程序。 下面是生成的程序的圖像。 功能描述 輸入網址&#xff08;URL&#xff09;。 輸入二維碼的名稱。 當單擊 QR 碼生成按鈕時&#xff0c;將使用 QRname 中輸入的字符將 QR 碼生成為圖像。 程序代碼 import qrcode import tkinterd…

java泛型:泛型類,泛型方法

今日記錄我的泛型使用&#xff0c;供后期查閱。 主要包含泛型類&#xff0c;泛型屬性&#xff0c;泛型方法&#xff0c;靜態方法中使用泛型。 public class GenericOperationResultRep<T> {private boolean success; // 是否操作成功。true&#xff0c;成功&#xff1b;f…

Oracle的錯誤信息幫助:Error Help

今天看手冊時&#xff0c;發現上面有個提示&#xff1a; Error messages are now available in Error Help. 點擊 View Error Help&#xff0c;顯示如下&#xff0c;其實就是oerr命令的圖形化版本&#xff1a; 點擊Database Error Message Index&#xff0c;以下界面等同于命令…

[Kadane算法,前綴和思想]元素和最大的子矩陣

元素和最大的子矩陣 題目描述 輸入一個n級方陣&#xff0c;請找到此矩陣的一個子矩陣&#xff0c;此子矩陣的各個元素的和是所有子矩陣中最大的&#xff0c;輸出這個子矩陣及這個最大的和。 關于輸入 首先輸入方陣的級數n&#xff0c; 然后輸入方陣中各個元素。 關于輸出 …

車載藍牙音樂流程簡單分析

關鍵類&#xff1a; /packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java /packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java 一、音樂播放狀態 CPP中通過JNI接口將接從…

Python中利用遺傳算法探索迷宮出路

更多資料獲取 &#x1f4da; 個人網站&#xff1a;ipengtao.com 當處理迷宮問題時&#xff0c;遺傳算法提供了一種創新的解決方案。本文將深入探討如何運用Python和遺傳算法來解決迷宮問題。迷宮問題是一個經典的尋路問題&#xff0c;尋找從起點到終點的最佳路徑。遺傳算法是一…

ActiveMQ斷線重連技巧,即通信高可用的配置

最近在做一個內部應用的時候&#xff0c;應用到了ActiveMQ作為服務之間消息傳遞&#xff0c;解耦服務之間的關聯&#xff0c;但是在應用的過程中遇到了連接斷線無法重連的問題&#xff0c;下面基于這個問題&#xff0c;深入了解一下ActiveMQ的一些相關原理和知識。 一、前置知…

springboot2 在Java項目中你們是如何配置時間格式響應給前端呢

在 Spring Boot 2 項目中配置時間格式&#xff0c;通常可以通過配置文件&#xff08;application.properties 或 application.yml&#xff09;或者通過 Java 代碼進行配置。以下是兩種常見的配置方式&#xff1a; 1. 通過配置文件配置時間格式&#xff1a; 在 application.pr…

mybaties plus插入數據,自動回顯 機制

結論&#xff1a;mybaties plus會將庫里數據自動回顯到 要插入的數據上 測試表格 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- 表結構 DROP TABLE IF EXISTS t_stu; CREATE TABLE t_stu (id int NOT NULL COMMENT id,name varchar(255) CHARACTER SET utf8mb4 COLLATE…

【PyTorch】計算設備

文章目錄 1. 介紹2. 查詢和使用 1. 介紹 CPU設備意味著所有物理CPU和內存&#xff0c; 這意味著PyTorch的計算將嘗試使用所有CPU核心。可以用以下方式表示&#xff1a; torch.device(cpu) GPU設備只代表一個GPU和相應的顯存。 torch.device(cuda)如果有多個GPU&#xff0c;我們…

Java解決矩陣對角線元素的和問題

Java解決矩陣對角線元素的和問題 01 題目 給你一個正方形矩陣 mat&#xff0c;請你返回矩陣對角線元素的和。 請你返回在矩陣主對角線上的元素和副對角線上且不在主對角線上元素的和。 示例 1&#xff1a; 輸入&#xff1a;mat [[1,2,3],[4,5,6],[7,8,9]] 輸出&#xff1a…

為什么流量對店鋪轉化率重要?亞馬遜、速賣通等跨境賣家通過自養號測評提升店鋪轉化率

亞馬遜、速賣通等電商平臺賣家非常清楚流量對店鋪轉化率的重要性&#xff0c;測評補單在跨境電商賣家中扮演著重要的角色&#xff0c;是一種必要的運營手段之一。在追求更好的產品曝光和更高的轉化率時&#xff0c;Listing的排名是關鍵因素之一。而在各個平臺的Listing中&#…

正確使用AFX_MANAGE_STATE宏管理MFC模塊狀態, AFX_MANAGE_STATE宏作用,真的很重要!!!

簡介&#xff1a; 在使用 MFC&#xff08;Microsoft Foundation Classes&#xff09;開發 DLL&#xff08;動態鏈接庫&#xff09;時&#xff0c;正確管理 MFC 模塊狀態是確保功能正常運行的關鍵。本文將深入探討使用 AFX_MANAGE_STATE 宏的重要性&#xff0c;以及在 DLL 中正確…

連接Redis報錯解決方案

連接Redis報錯&解決方案 問題描述&#xff1a;Could not connect to Redis at 127.0.0.1:6379: 由于目標計算機積極拒絕&#xff0c;無法連接。 問題原因&#xff1a;redis啟動方式不正確 解決方案&#xff1a; 在redis根目錄下打開命令行窗口&#xff0c;輸入命令redi…

聽GPT 講Rust源代碼--src/tools(12)

File: rust/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs 在Rust源代碼中&#xff0c;rust/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs文件的作用是定義和解析rust-analyzer的配置文件。該文件包含了各種配置項的數據結構和枚舉類型&#xf…

MQTT主題、通配符和最佳實踐

MQTT主題在MQTT生態系統非常重要&#xff0c;因為代理&#xff08;broker&#xff09;依賴主題確定哪個客戶端接收指定的主題。本文我們將聚集MQTT主題、MQTT通配符&#xff0c;詳細討論使用它們的最佳實踐&#xff0c;也會探究SYS主題&#xff0c;提供給代理&#xff08;broke…

【npm | npm常用命令及鏡像設置】

npm常用命令及鏡像設置 概述常用命令對比本地安裝全局安裝--save &#xff08;或 -S&#xff09;--save-dev &#xff08;或 -D&#xff09; 鏡像設置設置鏡像方法切換回npm官方鏡像選擇鏡像源 主頁傳送門&#xff1a;&#x1f4c0; 傳送 概述 npm致力于讓 JavaScript 開發變得…