內存可見性與指令重排序

文章目錄

  • 內存可見性
    • 內存可見性問題代碼演示
    • JMM(Java Memory Model)
  • 指令重排序
    • 指令重排序問題代碼演示
    • 指令重排序分析
  • volatile關鍵字
    • volatile 保證內存可見性 & 禁止指令重排序
    • volatile 不保證原子性

在上一節介紹線程安全問題的過程中,提到了產生線程安全的原因主要有

  1. 操作系統的線程隨機調度策略
  2. 對共享數據的寫操作
  3. 操作不具有原子性
  4. 內存可見性問題
  5. 指令重排序問題

這五點原因中線程的隨機調度是由操作系統調度模塊具體實現,無法干預,而多個線程對共享數據的寫操作,在某些情況下可以通過調整代碼結構進行避免。操作的原子性,可以通過加鎖來解決,這小節我們主要來看內存可見性和指令重排序是怎樣影響到線程安全的.

由于讀取內存,相比較讀取寄存器是一個非常慢的操作,編編譯器為了進一步提高代碼執行的效率,會在保持邏輯不變的前提下,調整生產的代碼內容,這樣的操作在單線程環境中不會有什么問題,但是,在多線程環境下,編譯器就可能會誤判,內存可見性和指令重排序都是有編譯器優化產生的問題

內存可見性

內存可見性問題代碼演示

我們先來觀察這段代碼:

import java.util.Scanner;public class Test6 {public static int isQuit = 0;// 內存可見性問題public static void main(String[] args) {Thread t1 = new Thread(() -> {while (isQuit == 0) {// 什么也不執行}System.out.println("t1 線程執行完畢");});Thread t2 = new Thread(() -> {System.out.println("請輸入isQuit:");Scanner scanner = new Scanner(System.in);isQuit = scanner.nextInt();System.out.println("t2線程執行完畢");});// 啟動線程t1.start();t2.start();}
}

執行結果~~

請輸入isQuit:
1
t2線程執行完畢

可以看到這里,輸入1之后線程并沒有執行完畢,那么不應該啊,isQuit的值不為0,t1線程應該會退出循環,可是并沒有。我們看一張圖。
在這里插入圖片描述
在這個過程中,我么看看兩個線程都做了什么。t1 線程在一直在讀取主內存中isQuit的值,由于循環體沒有執行任何邏輯,所以這個速度非常之快。t2線程先將isQuit讀入工作內存,然后修改值為1后寫回主內存。

如果就這樣看,那么在isQuit的值被修改后t1線程也應該隨之終止。但事實上Java在運行時,編譯器發現在大量讀取isQuit的值后,發現isQuit的值并沒有改變。于是就做出來一種激進的優化(讀取內存要比讀取寄存器慢得多),不再讀取內存,直接從寄存器中取值,這就導致了后續t2線程在我們輸入值后,isQuit的值的確是改變了,但是t1線程并沒有取讀取內存中的isQuit,這就導致了t1線程對isQuit的內存不可見

在單線程中,編譯器這樣的優化一般是沒有問題的,但是在并發場景下,就不得不考慮這樣優化后對代碼的影響。于是Java提供了volatile關鍵字,被這個關鍵字修飾后,編譯器將不會進行優化。

JMM(Java Memory Model)

我們先了解一下JMM, Java虛擬機(JVM)規范文檔中定義了Java內存模型.。目的是屏蔽掉各種硬件和操作系統的內存訪問差異(跨平臺),以實現讓Java程序在各種平臺下都能達到一致的并發效果。

  • 線程之間的共享變量存在 主內存 (Main Memory) - 相當于內存
  • 每一個線程都有自己的 “工作內存” (Work Memory) - 相當于寄存器
  • 當線程要讀取一個共享變量的時候, 會先把變量從主內存拷貝到工作內存, 再從工作內存讀取數據.
  • 當線程要修改一個共享變量的時候, 也會先修改工作內存中的副本, 再同步回主內存.

指令重排序

???? 和內存可見性一樣,指令重排序也是在一定條件下觸發的編譯器的”優化“,目的是提高代碼效率,編譯器在“保持邏輯不發生變化的情況下”,針對指令執行的順序進行調整,這就是指令重排序。

指令重排序問題代碼演示

class SingletonLazy {private static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() { }
}public class Demo22 {public static void main(String[] args) {}
}

在這個單例模式(懶漢模式)中,如果是第一次創建實例,那么會涉及到一個new操作。我們簡單的將new操作理解為三步:

  1. 申請內存空間
  2. 在內存空間上構造對象
  3. 把內存地址,復制給instance引用

指令重排序分析

在單線程下,先執行指令2,還是先執行指令3都可以,不影響最終的結果,但是在多線程下,就可能會出現問題。假設編譯器將new操作的執行順序優化為了 1 -> 3 -> 2,t1線程進入,創建單例,但是還沒構造對象,就已經將空引用返回(鎖已經釋放),這是如果t2線程進入,instance還是為空此時就可能會創建出多個實例。

解決方案和內存可見性一樣,使用volatile關鍵字,讓編譯器不要進行優化

volatile關鍵字

volatile 保證內存可見性 & 禁止指令重排序

代碼在寫入 volatile 修飾的變量時

  • 改變線程工作內存中volatile變量副本的值
  • 將改變后的副本的值從工作內存刷新到主內存

代碼在讀取 volatile 修飾的變量時

  • 從主內存中讀取volatile變量的最新值到線程的工作內存中
  • 從工作內存中讀取volatile變量的副本

????讀取內存相較于讀取寄存器來說,非常慢,使用volatile修飾雖然強制讀寫內存,但是保證了代碼的正確性,一般來說,不會犧牲正確新來換取效率。

volatile 不保證原子性

volatile 和 synchronized 有著本質的區別. synchronized 能夠保證原子性,volatile保證的是內存可見性,禁止指令重排序。volatile只是強制cpu讀取內存,但是不會保證操作的原子性(不可分割)。

不管是原子性、內存可見性還是指令重排序,都可能產生線程安全問題,我們在進行并發編程時一定要謹慎!!

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

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

相關文章

2023亞太杯數學建模B題思路 - 玻璃溫室中的微氣候法規

# 1 賽題 問題B 玻璃溫室中的微氣候法規 溫室作物的產量受到各種氣候因素的影響,包括溫度、濕度和風速[1]。其中,適 宜的溫度和風速是植物生長[2]的關鍵。為了調節玻璃溫室內的溫度、風速等氣候因素 , 溫室的設計通常采用帶有溫室風扇的通風系統&#x…

實驗4.數據全量、增量、比較更新

【實驗目的】 1.利用Kettle的“表輸入”,“表輸入出”,”JavaScript代碼”組件,實現數據全量更新。 2.熟練掌握“JavaScript代碼”,“表輸入”,“表輸入出”組件的使用,實現數據全量更新。 【實驗原理】 …

MATLAB算法實戰應用案例精講-【圖像處理】圖像縮放

目錄 前言 知識儲備 MATLAB圖像處理函數 數字數字圖像增強 數字數字圖像的變換

二級指針

*代表指針變量。int*為p的類型。故pp第一個*表示pp為指針int** pp,指向p的二級指針。 p中儲存a的地址,pp中儲存p的地址。 打印,printf中**pp的表示:pp中儲存的是p的地址,第一個*解引用地址p表示p的內容,p的…

Pickcode:教孩子們編碼的新視覺語言

Pickcode 通過視覺課程、聊天機器人、游戲和繪圖來教授編程。 Pickcode 是一種新的語言和編輯器,可以直觀地指導用戶編寫代碼來制作聊天機器人、動畫圖畫和游戲。Pickcode 旨在讓用戶在學習更高級的語言之前能夠充滿信心地開始學習編碼。 Pickcode 可視化編程語言…

回歸算法優化過程推導

假設存在一個數據集,包含工資、年齡及貸款額度三個維度的數據。我們需要根據這個數據集進行建模,從而在給定工資和年齡的情況下,實現對貸款額度的預測。其中,工資和年齡是模型構建時的兩個特征,額度是模型輸出的目標值…

被DDOS了怎么辦 要如何應對

DDoS攻擊的特點和類型 1. 特點 DDoS攻擊的特點是通過大量合法的請求或者無效的請求,消耗目標服務器的網絡帶寬和系統資源,使其無法正常運行。攻擊者通常使用多個主機發起攻擊,以達到更高的攻擊效果。 2. 常見類型 (1)S…

SPASS-ARIMA模型

基本概念 在預測中,對于平穩的時間序列,可用自回歸移動平均(AutoRegres- sive Moving Average, ARMA)模型及特殊情況的自回歸(AutoRegressive, AR)模型、移動平均(Moving Average, MA)模型等來擬合,預測該時間序列的未來值,但在實際的經濟預測中,隨機數據序列往往…

macos端文件夾快速訪問工具 Default Folder X 最新for mac

Default Folder X 是一款實用的工具,提供了許多增強功能和快捷方式,使用戶能夠更高效地瀏覽和管理文件。它的快速導航、增強的文件對話框、自定義設置和快捷鍵等功能,可以大大提升用戶的工作效率和文件管理體驗。 快速導航和訪問:…

Java開發中常用的工具類方法

一、JDK自帶工具包 (java.lang*.java.util.*等) 面是Java中jdk中附帶的一些常見工具類及其方法和示例的簡介 工具類 / 類所在包 常用方法 描述 示例 Arrays (java.util) sort() 對數組進行排序 Arrays.sort(arr); binarySearch() 在數組中執行…

S3的概念和使用

工作需要測試數據庫從 S3(Simple Storage Service)導入數據文件,公司有私有S3環境。 S3是一種對象存儲,數據模型很簡單,就是key-value,key就是一個任意字符串,value是一個文件。 S3的功能就是…

chromium證書校驗流程SM2WithSM3(C++源碼說明)

文章目錄 一、證書鏈二、證書鏈校驗過程三、證書鏈簽名校驗圖解四、C++源碼4.1 編譯TASSL4.2 代碼一,直接讀取簽名值方法4.3 代碼二(推薦)4.3.1 獲取證書的簽名數據4.3.2 獲取證書的簽名值4.3.3 從證書中獲取公鑰4.3.4 完整代碼4.3.5 代碼地址五、補充說明5.1 SM2的Z值算法以…

2023亞太杯數學建模B題思路分析 - 玻璃溫室中的微氣候法規

1 賽題 問題B 玻璃溫室中的微氣候法規 溫室作物的產量受到各種氣候因素的影響,包括溫度、濕度和風速[1]。其中,適 宜的溫度和風速是植物生長[2]的關鍵。為了調節玻璃溫室內的溫度、風速等氣候因素 , 溫室的設計通常采用帶有溫室風扇的通風系統&#xf…

《數學之美》第三版的讀書筆記一、主要是馬爾可夫假設、隱馬爾可夫模型、圖論深度/廣度、PageRank相關算法、TF-IDF詞頻算法

1、馬爾可夫假設 從19世紀到20世紀初,俄國有個數學家叫馬爾可夫他提出了一種方法,假設任意一個詞出現的概率只同它前面的詞有關。這種假設在數學上稱為馬爾可夫假設。 2、二元組的相對頻度 利用條件概率的公式,某個句子出現的概率等于每一個詞出現的條件概率相乘,于是可展…

【計算機網絡筆記】路由算法之層次路由

系列文章目錄 什么是計算機網絡? 什么是網絡協議? 計算機網絡的結構 數據交換之電路交換 數據交換之報文交換和分組交換 分組交換 vs 電路交換 計算機網絡性能(1)——速率、帶寬、延遲 計算機網絡性能(2)…

STM32_5(中斷)

中斷系統 中斷:在主程序運行過程中,出現了特定的中斷觸發條件(中斷源),使得CPU暫停當前正在運行的程序,轉而去處理中斷程序,處理完成后又返回原來被暫停的位置繼續運行中斷優先級:當…

如何用java的虛擬線程連接數據庫

我覺得這個很簡單 首先確保你idea支持jdk21. 然后把idea編譯成的目標字節碼設置為21版本的 然后編寫代碼。 創建虛擬線程的方式有: Runnable runnable () -> {System.out.println("Hello, world!"); };// 創建虛擬線程 Thread virtualThread Thre…

從0開始學習JavaScript--JavaScript迭代器

JavaScript迭代器(Iterator)是一種強大的編程工具,它提供了一種統一的方式來遍歷不同數據結構中的元素。本文將深入探討JavaScript迭代器的基本概念、用法,并通過豐富的示例代碼展示其在實際應用中的靈活性和強大功能。 迭代器的…

【計算思維】藍橋杯STEMA 科技素養考試真題及解析 2

1、蘭蘭有一些數字卡片,從 1 到 100 的數字都有,她拿出幾張數字卡片按照一定順序擺放。想一想,第 5 張卡片應該是 A、11 B、12 C、13 D、14 答案:C 2、按照下圖的規律,陰影部分應該填 A、 B、 C、 D、 答案&am…

spark如何配置checkpoint

1、sparkSession配置checkpoint的方法 # step1: 在conf中添加checkpoint的保存地址 val spark SparkSession.builder.appName(JobRegister.getJobName("xxx", s"xxxx")).config("hive.exec.dynamic.partition", "true").config(&quo…