JVM——Java內存模型

Java內存模型

在Java多線程編程中,Java內存模型(Java Memory Model, JMM)是理解程序執行行為和實現線程安全的關鍵。下面我們深入探討Java內存模型的內容。

Java內存模型概述

Java內存模型定義了Java程序中變量的內存操作規則,以及線程之間的通信語義。它屏蔽了底層硬件和操作系統的差異,為Java程序員提供了一個統一的內存訪問視圖。在JMM中,每個線程都有自己的工作內存,而共享變量存儲在主內存中。線程對變量的所有操作都必須通過工作內存進行,不能直接讀寫主內存中的變量。工作內存中的變量是主內存中變量的副本,線程之間的通信必須通過主內存進行。

happens-before關系與內存可見性

(一)happens-before關系定義

happens-before關系是Java內存模型中的核心概念之一,用于描述兩個操作之間的內存可見性。如果操作X happens-before操作Y,那么X的執行結果對Y是可見的。JMM通過定義一系列規則來確定兩個操作之間是否存在happens-before關系:

  1. 程序順序規則:在單線程中,按照代碼的順序,前面的操作happens-before后面的操 作。但這并不意味著指令不能重排序,只是重排序后必須保證單線程內的結果與順序執行一致。
  2. 監視器鎖規則:一個解鎖操作happens-before于后續對同一把鎖的加鎖操作。這保證了線程釋放鎖后,其他線程獲取鎖時能看到之前線程對共享變量的修改。
  3. volatile變量規則:對一個volatile變量的寫操作happens-before于后續對同一變量的讀操作。這確保了volatile變量的修改對其他線程立即可見。
  4. 線程啟動規則:線程的啟動操作happens-before于該線程的其他操作。這使得新啟動的線程能看到創建線程對共享變量的初始化操作。
  5. 線程終止規則:線程的所有操作happens-before于其他線程檢測到該線程已經終止(如通過Thread.join()或Thread.isAlive()判斷)。
  6. 中斷規則:一個線程的中斷操作happens-before于被中斷線程檢測到中斷事件。
  7. 傳遞性:如果操作A happens-before操作B,操作B happens-before操作C,那么操作A happens-before操作C。

(二)內存可見性問題

在多線程環境下,由于每個線程都有自己的工作內存,線程對共享變量的修改可能無法及時同步到主內存,導致其他線程無法看到最新的值。happens-before關系通過確保特定操作的有序性,解決了內存可見性問題。例如,通過使用volatile關鍵字修飾共享變量,可以保證對該變量的寫操作happens-before讀操作,從而確保線程間對該變量的修改可見。

(三)重排序與數據競爭

編譯器和處理器為了優化性能,可能會對指令進行重排序。但在多線程環境下,不合理的重排序可能導致數據競爭問題。例如,一個線程寫入共享變量后,另一個線程讀取該變量,但由于指令重排序,讀取操作可能在寫入操作之前執行,導致讀取到舊值。通過建立正確的happens-before關系,可以避免重排序帶來的數據競爭問題。

Java內存模型的底層實現

(一)內存屏障

Java內存模型通過內存屏障(memory barrier)來控制編譯器和處理器的重排序行為。內存屏障是一組指令,用于確保特定操作的執行順序,并強制刷新處理器緩存。常見的內存屏障類型包括:

  • LoadLoad屏障?:確保加載操作的順序。
  • LoadStore屏障?:確保加載操作在存儲操作之前完成。
  • StoreLoad屏障?:確保存儲操作在加載操作之前完成。
  • StoreStore屏障?:確保存儲操作的順序。

在JMM中,不同的happens-before關系對應不同的內存屏障插入策略。例如,volatile變量的寫操作前后會插入StoreLoad屏障,以防止重排序。

(二)即時編譯器優化與內存屏障

即時編譯器(JIT)在編譯Java字節碼為機器碼時,會根據JMM插入相應的內存屏障。對于不同的處理器架構,內存屏障的具體實現可能不同。例如,在X86架構上,由于其對內存訪問的強序一致性支持,某些內存屏障可以省略或簡化。即時編譯器需要根據具體的處理器特性,生成高效的機器碼,同時保證JMM的語義。

(三)處理器緩存與內存一致性

現代處理器使用緩存來提高內存訪問速度。每個處理器核心都有自己的緩存,這可能導致不同核心看到的內存值不一致。內存屏障通過強制刷新緩存,確保共享變量的修改對其他處理器核心可見。例如,當一個線程對共享變量進行修改后,內存屏障會將該變量的值從工作內存同步到主內存,并刷新其他處理器核心中的緩存行,使得其他線程能夠讀取到最新的值。

鎖與同步

(一)鎖的happens-before關系

鎖是Java中實現線程同步的重要機制。在JMM中,鎖的獲取和釋放操作具有特定的happens-before關系:

  • 解鎖happens-before加鎖?:一個線程對某個對象的解鎖操作happens-before于其他線程對該對象的加鎖操作。這確保了釋放鎖之前對該對象的修改對后續獲取鎖的線程可見。
  • 鎖的范圍內的操作順序?:在單線程中,鎖獲取之后的操作happens-before于鎖釋放之前的操作。這保證了鎖范圍內的操作具有一定的順序性。

(二)synchronized關鍵字的實現

synchronized關鍵字通過對象頭中的鎖標志位和Monitor來實現線程同步。當一個線程進入synchronized代碼塊時,它會獲取對象的鎖,并在退出代碼塊時釋放鎖。在JMM中,鎖的獲取和釋放操作會插入相應的內存屏障,確保線程之間的內存可見性。

(三)鎖的優化與性能

為了提高性能,JVM對鎖進行了多種優化,如偏向鎖、輕量級鎖和重量級鎖的轉換。偏向鎖通過記錄線程ID,減少同一線程多次獲取鎖的開銷;輕量級鎖通過CAS操作實現鎖的獲取和釋放,避免進入重量級的Monitor等待隊列。這些優化措施在保證線程安全的同時,提高了程序的執行效率。

volatile字段

(一)volatile的內存語義

volatile關鍵字是Java中實現輕量級線程同步的重要工具。被volatile修飾的變量具有以下特性:

  • 可見性?:對volatile變量的修改對其他線程立即可見。JMM通過在volatile變量的讀寫操作前后插入內存屏障,確保修改操作及時同步到主內存,并刷新其他線程的工作內存。
  • 有序性?:禁止編譯器和處理器對volatile變量的讀寫操作進行重排序。這保證了程序的執行順序與代碼的邏輯順序一致,避免了由于重排序導致的內存可見性問題。

(二)volatile的使用場景與限制

  • 使用場景?:volatile適用于單個變量的狀態標記,如表示任務完成、線程停止等布爾標志。例如,一個線程通過修改volatile布爾變量來通知其他線程任務已完成。
  • 限制?:volatile不能保證復合操作的原子性。例如,對volatile變量進行遞增操作(i++)并不是原子操作,可能會出現線程安全問題。在需要保證復合操作原子性的場景下,應使用鎖或其他同步機制。

(三)volatile底層實現與性能

在底層實現上,JVM通過在volatile變量的讀寫操作中插入內存屏障來保證內存可見性和禁止重排序。在X86架構中,volatile寫操作會生成lock前綴的指令(如lock addl $0x0, (%rsp)),該指令具有內存屏障的效果,強制刷新處理器緩存。volatile讀操作則通過加載主內存中的最新值來保證可見性。與鎖相比,volatile的性能開銷較低,但在頻繁讀寫的情況下,由于每次都需要訪問主內存,可能會導致性能瓶頸。

final字段與安全發布

(一)final字段的內存語義

final關鍵字修飾的字段具有特殊的內存語義:

  • 寫操作的有序性?:在JMM中,final字段的寫操作后會插入一個StoreStore屏障,禁止將final字段的寫操作重排序到構造函數返回之前。這確保了新建對象的final字段在對象引用被其他線程獲取后不會被修改,并且其他線程能夠看到final字段的正確初始化值。
  • 讀操作的可見性?:一旦一個對象的引用被發布(即對象引用被其他線程獲取),該對象的final字段的初始化值對其他線程可見。這是因為final字段的寫操作與對象引用的寫操作之間存在happens-before關系。

(二)安全發布對象

安全發布(safe publication)是指確保一個對象的初始化完成,并且該對象的所有字段(包括非final字段)的值對其他線程可見。JMM提供了以下幾種安全發布對象的方式:

  • 使用final關鍵字?:final字段的寫操作與對象引用的寫操作之間存在happens-before關系,確保對象的final字段在對象引用被發布后對其他線程可見。此外,JMM還保證,一旦對象的引用被發布,該對象的其他字段的值至少會看到構造函數中對這些字段的最后設置值。
  • 使用volatile關鍵字修飾對象引用?:將對象引用聲明為volatile,可以確保對象的引用對其他線程可見,并且其他線程在讀取該引用后能看到對象的最新狀態。volatile對象引用的寫操作happens-before讀操作,保證了對象的可見性。
  • 使用同步機制?:通過鎖來保護對象的引用和對象的初始化過程,確保對象的引用和狀態在鎖保護下對其他線程可見。例如,在構造函數中加鎖,并在發布對象引用時確保鎖的正確獲取和釋放。

(三)final字段與不可變對象

final字段常用于構建不可變對象。不可變對象一旦創建后,其狀態不能被修改。通過將對象的字段聲明為final,并在構造函數中完成初始化,可以確保對象的不可變性。不可變對象具有線程安全的特性,因為它們的狀態不會改變,無需額外的同步措施。例如,Java中的String類就是一個典型的不可變對象,其字符數組被聲明為final,確保字符串的內容在創建后不可修改。

實際案例與實踐建議

(一)避免指令重排序導致的錯誤

在多線程環境下,指令重排序可能導致程序行為與預期不符。例如,考慮以下代碼:

class UnsafePublication {int x = 1;int y = 2;public static void main(String[] args) {UnsafePublication up = new UnsafePublication();System.out.println("x: " + up.x + ", y: " + up.y);}
}

如果對象up的引用被發布前,其字段x和y的初始化操作被重排序,其他線程可能看到x和y的默認值(0)而不是初始化值。為避免此類問題,應使用安全發布機制,如將字段聲明為final,或使用同步機制確保對象正確發布。

(二)使用volatile確保變量可見性

在需要頻繁更新的狀態標志場景下,volatile是合適的選擇。例如:

public class StopThread {private volatile boolean stopRequested = false;public void run() {while (!stopRequested) {// 執行任務}}public void stop() {stopRequested = true;}
}

通過將stopRequested聲明為volatile,確保run方法中的循環條件能夠及時看到stop方法對變量的修改,從而安全地停止線程。

(三)利用final構建不可變對象

構建不可變對象可以簡化線程安全設計。例如:

public final class ImmutableObject {private final int value;public ImmutableObject(int value) {this.value = value;}public int getValue() {return value;}
}

ImmutableObject的value字段被聲明為final,確保對象創建后其值不可修改。這使得該對象可以安全地在多個線程間共享,無需額外的同步措施。

總結

Java內存模型是Java多線程編程的基石,它通過happens-before關系、內存屏障、鎖、volatile和final等機制,為開發者提供了控制內存可見性和線程同步的工具。深入理解JMM的原理和實踐,能夠幫助開發者避免常見的并發編程錯誤,設計出高效、可靠的多線程應用。在實際開發中,應根據具體的場景選擇合適的同步機制,如使用volatile確保變量可見性、final構建不可變對象以及鎖保護共享資源等,以實現高效的線程安全。

在閱讀本文以后,還可以拓展閱讀我之前寫的什么是 Java 內存模型?這篇文章。

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

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

相關文章

nRF Connect SDK system off模式介紹

目錄 概述 1. 軟硬件環境 1.1 軟件開發環境 1.2 硬件環境 2 System Off 模式 2.1 模式介紹 2.2 注意事項 3 功能實現 3.1 框架結構介紹 3.2 代碼介紹 4 功能驗證 4.1 編譯和下載代碼 4.2 測試 4.3 使能CONFIG_APP_USE_RETAINED_MEM的測試 5 main.c的源代碼文件…

白楊SEO:如何查看百度、抖音、微信、微博、小紅書、知乎、B站、視頻號、快手等7天內最熱門話題及流量關鍵詞有哪些?使用方法和免費工具推薦以及注意事項【干貨】

大家好,我是白楊SEO,專注SEO十年以上,全網SEO流量實戰派,AI搜索優化研究者。 (溫馨提醒:本文有點長,看不完建議先收藏或星標,后面慢慢看哈) 最近,不管是在白…

2025 Mac常用軟件安裝配置

1、homebrew 2、jdk 1、使用brew安裝jdk: brew install adoptopenjdk/openjdk/adoptopenjdk8 jdk默認安裝位置在 /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home 目錄。 2、配置環境變量: vim ~/.zshrc# Jdk export JAVA_HOM…

Linux 內核學習(6) --- Linux 內核基礎知識

目錄 Linux 內核基礎知識進程調度內存管理虛擬文件系統和網絡接口進程間通信Linux 內核編譯Makefile 和 Kconfig內核Makefile內核Kconfig 配置項標識的寫法depend 關鍵字select 關鍵字表達式邏輯關系Kconfig 其他語法 配置文件的編譯Linux 內核引導方法Booloader 定義Linux 內核…

常見匯編代碼及其指令

1. 數據傳輸指令 1.1. mov 作用:將數據從源操作數復制到目標操作數。語法:mov dest, src mov eax, 10 ; 將立即數 10 存入 eax 寄存器 mov ebx, eax ; 將 eax 的值復制到 ebx mov [ecx], eax ; 將 eax 的值寫入 ecx 指向的內存地址 1.2. …

STM32基礎教程——軟件SPI

目錄 前言 技術實現 接線圖 代碼實現 技術要點 引腳操作 SPI初始化 SPI起始信號 SPI終止信號 SPI字節交換 宏替換命令 W25Q64寫使能 忙等待 讀取設備ID號和制造商ID 頁寫入 數據讀取 實驗結果 問題記錄 前言 SPI(Serial Peripheral Interf…

(B題|礦山數據處理問題)2025年第二十二屆五一數學建模競賽(五一杯/五一賽)解題思路|完整代碼論文集合

我是Tina表姐,畢業于中國人民大學,對數學建模的熱愛讓我在這一領域深耕多年。我的建模思路已經幫助了百余位學習者和參賽者在數學建模的道路上取得了顯著的進步和成就。現在,我將這份寶貴的經驗和知識凝練成一份全面的解題思路與代碼論文集合…

無網絡環境下配置并運行 word2vec復現.py

需運行文件 # -*- coding: utf-8 -*- import torch import pandas as pd import jieba import torch import torch.nn as nn from tqdm import tqdm from torch.utils.data import DataLoader,Dataset from transformers import AutoTokenizer,AutoModeldef get_stop_word():w…

讀《暗時間》有感

讀《暗時間》有感 反思與筆記 這本書還是我無意中使用 ima 給我寫職業規劃的時候給出的,由于有收藏的習慣,我就去找了這本書。當讀到第一章暗時間的時候給了我很大的沖擊,我本身就是一個想快速讀完一本書的人,看到東西沒有深入思…

ubuntu安裝Go SDK

# 下載最新版 Go 安裝包(以 1.21.5 為例) wget https://golang.google.cn/dl/go1.21.5.linux-amd64.tar.gz # 解壓到系統目錄(需要 root 權限) sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz # 使用 Go 官方安裝腳本…

FFmpeg(7.1版本)編譯生成ffplay

FFmpeg在編譯的時候,沒有生成ffplay,怎么辦? 1. 按照上一篇文章:FFmpeg(7.1版本)在Ubuntu18.04上的編譯_ffmpeg-7.1-CSDN博客 在build.sh腳本里配置了ffplay 但是,實際上卻沒有生成ffplay,會是什么原因呢? 2. 原因是編譯ffplay的時候,需要一些依賴庫 sudo apt-get i…

【Python 函數】

Python 中的函數(Function)是可重復使用的代碼塊,用于封裝特定功能并提高代碼復用性。以下是函數的核心知識點: 一、基礎語法 1. 定義函數 def greet(name):"""打印問候語""" # 文檔字符串&…

7. HTML 表格基礎

表格是網頁開發中最基礎也最實用的元素之一,盡管現代前端開發中表格布局已被 CSS 布局方案取代,但在展示結構化數據時,表格依然發揮著不可替代的作用。本文將基于提供的代碼素材,系統講解 HTML 表格的核心概念與實用技巧。 一、表格的基本結構 一個完整的 HTML 表格由以下…

極狐GitLab 命名空間的類型有哪些?

極狐GitLab 是 GitLab 在中國的發行版,關于中文參考文檔和資料有: 極狐GitLab 中文文檔極狐GitLab 中文論壇極狐GitLab 官網 命名空間 命名空間在極狐GitLab 中組織項目。因為每一個命名空間都是單獨的,您可以在多個命名空間中使用相同的項…

powershell批處理——io校驗

powershell批處理——io校驗 在刷題時,時常回想,OJ平臺是如何校驗競賽隊員提交的代碼的,OJ平臺并不看代碼,而是使用“黑盒測試”,用測試數據來驗證。對于每題,都事先設定了很多組輸入數據(data…

前端面經-webpack篇--定義、配置、構建流程、 Loader、Tree Shaking、懶加載與預加載、代碼分割、 Plugin 機制

看完本篇你將基本了解webpack!!! 目錄 一、Webpack 的作用 1、基本配置結構 2、配置項詳解 1. entry —— 構建入口 2. output —— 輸出配置 3. mode:模式設置 4. module:模塊規則 5. plugins:插件機制 6. resolve:模塊解析配置(可選) 7. devServer:開發服務器…

面試算法刷題練習1(核心+acm)

3. 無重復字符的最長子串 核心代碼模式 class Solution {public int lengthOfLongestSubstring(String s) {int lens.length();int []numnew int[300];int ans0;for(int i0,j0;i<len;i){num[s.charAt(i)];while(num[s.charAt(i)]>1){num[s.charAt(j)]--;j;}ansMath.max…

拉削絲錐,螺紋類加工的選擇之一

在我們的日常生活中&#xff0c;螺紋連接無處不在&#xff0c;從簡單的螺絲釘到復雜的機械設備&#xff0c;都離不開螺紋的精密加工。今天&#xff0c;給大家介紹一種的螺紋刀具——拉削絲錐&#xff1a; 一、拉削絲錐的工作原理 拉削絲錐&#xff0c;聽起來有點陌生吧&#…

數據清洗-電商雙11美妝數據分析(二)

1.接下來用seaborn包給出每個店鋪各個大類以及各個小類的銷量銷售額 先觀察銷量&#xff0c;各店小類中銷量最高的是相宜本草的補水類商品以及妮維雅的清潔類商品&#xff0c;這兩類銷量很接近。而銷售額上&#xff0c;相宜本草的補水類商品比妮維雅的清潔類商品要高得多&#…

【上位機——MFC】對話框

對話框的使用 1.添加對話框資源 2.定義一個自己的對話框類(CMyDlg)&#xff0c;管理對話框資源&#xff0c;派生自CDialog或CDialogEx均可 對話框架構 #include <afxwin.h> #include "resource.h"class CMyDlg :public CDialog {DECLARE_MESSAGE_MAP() publi…