volatile關鍵字的作用 以及 單例模式(餓漢模式與懶漢模式的區別及改進)

文章目錄

  • 💡volatile保證內存可見性
  • 💡單例模式
  • 💡餓漢模式
  • 💡懶漢模式
  • 💡懶漢模式多線程版
  • 💡volatile防止指令重排序

💡volatile保證內存可見性

Volatile 修飾的變量能夠保證“內存可見性”以及防止”指令重排序“

什么是可見性:當某個線程修改了某個共享變量,其他的線程是否可以看見修改后的內容;

因為訪問一個變量時,CPU就會先把變量從內存中讀出來,然后放到CPU寄存器中進行運算;運算完之后,再將新的數據在內存中進行刷新;

在這里插入圖片描述

對于操作系統來講,讀內存的速度是比較慢的,(注意:這里的慢 是 相對于寄存器而言的,就像,讀內存要比讀硬盤快上千倍或上萬倍,讀寄存器比讀內存快上千倍上萬倍), 這時候就會影響執行的效率。為了提高效率,編譯器就會對代碼進行一個優化,把讀內存的操作優化成讀寄存器,從而減少對內存的讀取,提高整個效率;

舉個例子:

代碼目的:創建兩個線程,通過線程2修改線程1的循環判斷條件來終止線程1的循環執行

public class Demo1 {private static int flag = 0;public static void main(String[] args) {Thread thread1 = new Thread(() -> {while(flag == 0) {//當循環不等于0時,一直循環,直到flag被改變}System.out.println("thread1 執行結束");});Thread thread2 = new Thread(() -> {Scanner in = new Scanner(System.in);System.out.println("更改flag:");//通過更改flag終止線程1的執行flag = in.nextInt();System.out.println("輸入成功");});thread1.start();thread2.start();}
}

在這里插入圖片描述

根據結果可以看到,線程1并沒有終止循環,這就是“內存可見性”所導致的線程不安全👇

在這里插入圖片描述

在多線程的環境下(在單線程環境下沒問題),如果編譯器作出優化,可能就會導致bug,雖然提高了效率,但是最后結果卻是錯誤的,

此時就需要程序員使用Volatile關鍵字告訴編譯器,不需要進行代碼優化:

直接給flag加上Volatile即可

在這里插入圖片描述

注意, volatile只能夠保證內存可見性問題,不會保證代碼的原子性,但是Synchronized既可以保證內存可見性,也能保證原子性;

以上就是volatile能夠保證內存可見性的講解

💡單例模式

單例模式是一種經典的設計模式了,它的作用就是保證在有些場景下,需要一個類只能有一個對象,而不能有多個對象,比如像你以后娶媳婦,你娶媳婦肯定是只能娶一個,而不能娶兩個;

但是,問題來了,一個類只需要一個對象,那在new對象的時候只new一次對象不就可以了么,為什么還要弄個這么麻煩的東西呢?

因為啊,只new一次對象確實是只有一個,但是呢,如果你在寫代碼的過程中忘了呢,然后又new了一次,這種概率是很大的,畢竟,人是最不靠譜的動物😅,就像是有一句話說的好:寧可相信世界上有鬼,也不要相信男人的那張嘴😂,所以的,為了防止這種失誤發生,就有了單例模式,在Java中也有許多類似的機制,比如final,就會保證修飾的變量肯定是不能改變的;@override,保證你這方法肯定是一個重寫方法;這些都是在語法方面進行了一些限制,但是,在語法方面,對于單例并沒有特定的語法,所以,這里就通過編程技巧來達到類似的限制效果;

單例模式的兩種實現方式:

💡餓漢模式

1.在類中實例化類的對象,給外界提供一個方法來使用這個對象;

2.將構造方法用private修飾,保證在類外不能再實例化這個類對象

public class SingleTon {//在類的內部實例化對象public static SingleTon instance = new SingleTon();//定義一個方法,用來獲取這個對象//后序如果類外的代碼想要使用對象時,直接調用這個方法即可public static SingleTon getInstance() {return instance;}//設置一個私有的構造方法,保證在這個類外無法實例化這個對象private SingleTon(){}
}

在這里插入圖片描述

可以看到,這里的對象被static修飾,所以在類被加載的時候創建,創建的時機就比較早,并且被static修飾的對象只會被創建一次,所以這種在類加載時就創建實例的模式稱為餓漢模式

💡懶漢模式

懶漢模式單線程版:

這樣的寫法與上面的相同點就是:同樣在類外不能再第二次實例化對象,不同點是:將創建對象的時機放在getInstance方法中,這樣在類加載的時候就不會創造實例,而是當第一次調用這個方法時才會去創建

public class SingleTon {public static SingleTon instance = null;//定義一個方法,用來獲取這個對象//后序如果類外的代碼想要使用對象時,直接調用這個方法即可public static SingleTon getInstance() {//懶漢模式if(instance == null) {instance = new SingleTon();}return instance;}//設置一個私有的構造方法,保證在這個類外無法實例化這個對象private SingleTon(){}
}

在這里插入圖片描述

💡懶漢模式多線程版

在線程安全方面,上面的餓漢模式是在多線程下是安全的,而懶漢模式在多線程下是不安全的;

因為,如果多個線程同時訪問一個變量,那么不會出現不安全問題,如果多個線程同時修改一個變量,就有可能出現不安全問題;

餓漢模式下,只進行了訪問,沒有涉及到修改

在這里插入圖片描述

懶漢模式下,不僅進行了訪問,還涉及了修改,那么下面就講解以下懶漢模式在多線程下如何會產生不安全

在這里插入圖片描述

在這里插入圖片描述

既然出現了不安全問題,那么如何將懶漢模式修改成安全的呢?

💡方法:進行加鎖,使線程安全

在這里插入圖片描述

但是,如果鎖加在這個地方,仍然是不安全的,因為,這樣還是會進行穿插執行,如果兩個并發的進入的 if 語句中,那么,就會進行鎖競爭,假設,thread1 獲取到了鎖,thread2 在阻塞等待,等到 thread1 創建一次對象,釋放鎖后,thread2 就又會載獲取到鎖,進行創建對象,所以,這個加鎖操作并沒有保證它是一個整體(非原子性)

在這里插入圖片描述

所以說,并不是加了鎖就安全,只有鎖加對了才會安全,在加鎖的時候要保證以下幾方面:

  1. 鎖的 {} 的范圍是合理的,能夠把需要作為整體的每個部分都包括進去;

  2. 鎖的對象能夠起到鎖競爭的效果;

懶漢模式多線程版改進👇

將if語句和new都放在鎖里面成為一個整體,這樣就避免了會穿插執行;

    public static SingleTon getInstance() {synchronized (SingleTon.class) {if(instance == null) {instance = new SingleTon();}}return instance;}

但是上述代碼還有一個問題,每當調用getInstance時,都會嘗試去進行加鎖,而加鎖是一個開銷很大的操作,而懶漢模式之所以會出現線程不安全問題,是因為只是在第一次調用getInstance方法new對象時,可能會出現問題,但是,只要new完對象以后,就不用再進行鎖競爭了,直接訪問就可以了,所以再次進行優化👇:

    public static SingleTon getInstance() {//在最外面在進行一次判斷if(instance == null) {synchronized (SingleTon.class) {if(instance == null) {instance = new SingleTon();}}}return instance;}

在第一次實例化對象后,以后再調用個getInstance方法時,就不會再創建對象,而且也不會再去獲取鎖,因為,第一個if判斷語句都不會進去,所以不會執行到加鎖的語句;

上面的單例模式看著好像是完全沒問題了,但是,還是有一個問題,就是可能會觸發指令重排序問題,所以就需要使用volatile解決指令重排序問題

💡volatile防止指令重排序

指令重排序:編譯器會保證在你代碼邏輯不變的情況下,對代碼進行優化,使代碼的性能得到提高,這樣的操作稱為指令重排序;

舉個例子:

在這里插入圖片描述

在這里插入圖片描述

在代碼中,在實例化對象這一步可能會出現指令重排序問題,下面就來講解一下為什么👇

在這里插入圖片描述

在這里插入圖片描述

對于上述的指令重排序問題,解決方案就是:使用volatile關鍵字修飾singleTon

**線程安全的單例模式(懶漢模式)**👇

public class SingleTon {//使用volatile關鍵字修飾,防止指令重排序public static volatile SingleTon singleTon = null;public static SingleTon getSingleTon() {if(singleTon == null) {synchronized (SingleTon.class) {if(singleTon == null) {singleTon = new SingleTon();}}}return singleTon;}private SingleTon() {};}

💡💡這里再次提醒,使用單例模式要注意三個要點:

  • 加鎖
  • 兩層if判斷
  • 使用volatile修飾引用,防止指令重排序

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

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

相關文章

解決連接工具Mobaxterm連接錯誤Connection timed out----VMware里Linux端口號固定

錯誤:Connection timed out 原因: 沒有固定ip,網絡斷開,再次連接就是新的IP 解決: 再次測試連接,彈出這個就代表成功了

CUDA 中的線程組織

明朝那些事中有一句話:我之所以寫徐霞客是想告訴你,所謂千秋霸業萬古流芳,與一件事相比,其實都算不了什么,這件事情就是——用你喜歡的方式度過一生。 我們以最簡單的 CUDA 程序:從 GPU 中輸出 Hello World…

實現一個簡單的哈希映射功能

說在前面 🎈哈希表大家應該都經常用到吧,那么大家有沒有想過哈希表是怎么實現的呢?今天讓我們一起從一道簡單的題目來初步了解一個哈希表的簡單原理。 目的 不使用任何內建的哈希表庫設計一個哈希映射(HashMap)。 實…

bert 相似度任務訓練完整版

任務 之前寫了一個相似度任務的版本:bert 相似度任務訓練簡單版本,faiss 尋找相似 topk-CSDN博客 相似度用的是 0,1,相當于分類任務,現在我們相似度有評分,不再是 0,1 了,分數為 0-5,數字越大…

EasyRecovery易恢復2024免費文件數據恢復軟件下載

一、軟件概述 EasyRecovery易恢復中文文件數據恢復軟件是一款專為中文用戶設計的強大數據恢復工具。該軟件致力于幫助用戶從各種存儲設備中恢復因各種原因丟失的中文文件,如文檔、圖片、視頻、音頻等。憑借其核心技術和多年的研發經驗,EasyRecovery易恢…

C語言計算誤碼率

#include <stdio.h> #include <stdlib.h> bool dayintrue; //是否打印 int main(){ int i,k,g0; int n10,n20; int good0,bad0; double rate; (dayin)? printf("打印具體數據\n"):printf("不打印具體數據\n\n");…

STM32-SPI通信協議

串行外設接口SPI&#xff08;Serial Peripheral Interface&#xff09;是由Motorola公司開發的一種通用數據總線。 在某些芯片上&#xff0c;SPI接口可以配置為支持SPI協議或者支持I2S音頻協議。 SPI接口默認工作在SPI方式&#xff0c;可以通過軟件把功能從SPI模式切換…

Python·算法·每日一題(3月4日)最長公共前綴

題目 編寫一個函數來查找字符串數組中的最長公共前綴。 如果不存在公共前綴&#xff0c;返回空字符串 “”。 示例 示例 1&#xff1a; 輸入&#xff1a;strs ["flower","flow","flight"] 輸出&#xff1a;"fl"示例 2&#xff1a;…

【數據結構與算法】常見排序算法(Sorting Algorithm)

文章目錄 相關概念1. 冒泡排序&#xff08;Bubble Sort&#xff09;2. 直接插入排序&#xff08;Insertion Sort&#xff09;3. 希爾排序&#xff08;Shell Sort&#xff09;4. 直接選擇排序&#xff08;Selection Sort&#xff09;5. 堆排序&#xff08;Heap Sort&#xff09;…

【腦科學相關合集】有關腦影像數據相關介紹的筆記及有關腦網絡的筆記合集

【腦科學相關合集】有關腦影像數據相關介紹的筆記及有關腦網絡的筆記合集 前言腦模板方面相關筆記清單 基于腦網絡的方法方面數據基本方面 前言 這里&#xff0c;我將展開有關我自己關于腦影像數據相關介紹的筆記及有關腦網絡的筆記合集。其中&#xff0c;腦網絡的相關論文主要…

【錯誤處理】【Hive】【Spark】ERROR FileFormatwriter: Aborting job null.

問題背景 近日&#xff0c;使用 Spark 在讀寫 Hive 表時發生了報錯&#xff1a;Aborting job null&#xff0c;如果怎么都使用不了那張表的話&#xff0c;大概率是那張表有臟數據&#xff0c;導致整張表無法正常使用。 ERROR FileFormatwriter: Aborting job null.解決方法 …

SpringBoot 如何快速過濾出一次請求的所有日志?

前言 在現網出現故障時&#xff0c;我們經常需要獲取一次請求流程里的所有日志進行定位。如果請求只在一個線程里處理&#xff0c;則我們可以通過線程ID來過濾日志&#xff0c;但如果請求包含異步線程的處理&#xff0c;那么光靠線程ID就顯得捉襟見肘了。 華為IoT平臺&#x…

《自然》:人工智能在創造性思維方面超越人類

發散性思維被認為是創造性思維的指標。ChatGPT-4 在三項有151名人類參與的**發散思維測試中&#xff0c;**展現出比人類更高水平的創造力&#xff0c;結果顯示人工智能在創意領域持續發展。 發散性思維的特點是能夠針對沒有預期解決方案的問題提出獨特的解決方案&#xff0c;例…

TOMCAT的安裝與基本信息

一、TOMCAT簡介 Tomcat 服務器是一個免費的開放源代碼的Web 應用服務器&#xff0c;屬于輕量級應用服務器&#xff0c;在中小型系統和并發訪問用戶不是很多的場合下被普遍使用&#xff0c;是開發和調試JSP 程序的首選。對于一個初學者來說&#xff0c;可以這樣認為&#xff0c…

IO 與 NIO

優質博文&#xff1a;IT-BLOG-CN 一、阻塞IO / 非阻塞NIO 阻塞IO&#xff1a;當一條線程執行read()或者write()方法時&#xff0c;這條線程會一直阻塞直到讀取到了一些數據或者要寫出去的數據已經全部寫出&#xff0c;在這期間這條線程不能做任何其他的事情。 非阻塞NIO&…

記錄踩過的坑-macOS下使用VS Code

目錄 切換主題 安裝插件 搭建Python開發環境 裝Python插件 配置解釋器 打開項目 打開終端 切換主題 安裝插件 方法1 方法2 搭建Python開發環境 裝Python插件 配置解釋器 假設解釋器已經通過Anaconda建好&#xff0c;只需要在VS Code中關聯。 打開項目 打開終端

ArmV8架構

Armv8/armv9架構入門指南 — Armv8/armv9架構入門指南 v1.0 documentation 上面只是給了一個比較好的參考文檔 其他內容待補充

網絡-httpclient調用https服務端繞過證書的方法

httpclient調用https服務端繞過證書的方法 在日常開發或者測試中&#xff0c;通常會遇到需要用httpclient客戶端調用對方http是服務器的場景&#xff0c;由于沒有證書&#xff0c;所以直接是無法調用的。采用下面的方法可以繞過證書驗證&#xff1a; TrustManager[] trustAll…

AutoSAR(基礎入門篇)13.5-Mcal Mcu時鐘的配置

目錄 一、EB的Mcu模塊結構 二、時鐘的配置 對Mcu的配置主要就是其時鐘樹的配置,但是EB將GTM、ERU等很多重要的模塊也都放在了Mcu里面做配置,所以這里的Mcu是一個很龐大的模塊, 我們目前只講時鐘樹的部分 一、EB的Mcu模塊結構 1. 所有的模塊都基本上是這么些配置類別,Mc…

單詞級文本攻擊—論文閱讀

TAAD2.2論文概覽 0.前言1-101.Bridge the Gap Between CV and NLP! A Gradient-based Textual Adversarial Attack Frameworka. 背景b. 方法c. 結果d. 論文及代碼 2.TextHacker: Learning based Hybrid Local Search Algorithm for Text Hard-label Adversarial Attacka. 背景b…