JMM內存模型之happens-before闡述

文章目錄

  • 一、happens-before的定義
  • 二、happens-before的規則
    • 1. 程序順序規則:
    • 2. 監視器鎖規則:
    • 3. volatile變量規則:
    • 4. 傳遞性:
    • 5. start()規則:
    • 6. join()規則:

一、happens-before的定義

  • 如果一個操作happens-before另一個操作,那么第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
  • 兩個操作之間存在happens-before關系,并不意味著Java平臺的具體實現必須要按照 happens-before關系指定的順序來執行。如果重排序之后的執行結果,與按happens-before關系來執行的結果一致,那么這種重排序并不非法

二、happens-before的規則

1. 程序順序規則:

一個線程中的每個操作,happens-before于該線程中的任意后續操作。

程序順序規則(Program Order Rule)指的是在單個線程內,按照程序的順序,前面的操作 happens-before 后面的操作。這意味著一個線程中的每個操作都會在該線程中的任意后續操作之前發生。

下面是一個簡單的Java程序示例,展示了程序順序規則的應用:

public class ProgramOrderDemo {private int count = 0;public void increment() {count++; // 操作1}public void printCount() {System.out.println("Count: " + count); // 操作2}public static void main(String[] args) {ProgramOrderDemo demo = new ProgramOrderDemo();Thread thread1 = new Thread(() -> {demo.increment(); // 線程1中的操作1demo.printCount(); // 線程1中的操作2});Thread thread2 = new Thread(() -> {demo.increment(); // 線程2中的操作1demo.printCount(); // 線程2中的操作2});thread1.start();thread2.start();}
}

在上述示例中,我們創建了兩個線程:thread1和thread2,它們都會調用ProgramOrderDemo類中的increment()和printCount()方法。

根據程序順序規則,線程1中的操作1(count++)happens-before 線程1中的操作2(System.out.println("Count: " + count)),同樣線程2中的操作1(count++)happens-before 線程2中的操作2(System.out.println("Count: " + count))。

這意味著在每個線程中,count++操作一定會在System.out.println("Count: " + count)操作之前發生。因此,我們可以確保在每個線程中打印的count值是遞增的。

請注意,盡管兩個線程并行執行,但由于程序順序規則的存在,各個線程內部的操作順序是有序的,不會出現線程間的競爭條件。

2. 監視器鎖規則:

對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖。

監視器鎖規則(Monitor Lock Rule)指的是對一個鎖的解鎖操作 happens-before 于隨后對同一個鎖的加鎖操作。這個規則確保了在多線程環境下,對共享資源的同步訪問。

下面是一個簡單的Java程序示例,展示了監視器鎖規則的應用:

public class MonitorLockDemo {private int count = 0;private Object lock = new Object();public void increment() {synchronized (lock) {count++; // 線程1中的操作1}}public void printCount() {synchronized (lock) {System.out.println("Count: " + count); // 線程2中的操作2}}public static void main(String[] args) {MonitorLockDemo demo = new MonitorLockDemo();Thread thread1 = new Thread(() -> {demo.increment(); // 線程1中的操作1});Thread thread2 = new Thread(() -> {demo.printCount(); // 線程2中的操作2});thread1.start();thread2.start();}
}

在上述示例中,我們創建了兩個線程:thread1和thread2,它們都會調用MonitorLockDemo類中的increment()和printCount()方法。在這個類中,我們使用了一個對象lock作為鎖。

根據監視器鎖規則,線程1中的操作1(count++)happens-before 線程2中的操作2(System.out.println("Count: " + count))。

這意味著在線程1中,對鎖的解鎖操作一定會在線程2中對同一個鎖的加鎖操作之前發生。因此,我們可以確保在執行打印操作時,count的值是線程1已經更新過的。

請注意,通過使用synchronized關鍵字和共享的鎖對象,我們確保了對count的訪問是同步的,避免了競態條件和數據不一致的問題。這是因為監視器鎖規則確保了解鎖操作 happens-before 加鎖操作,從而確保了對共享資源的正確同步訪問。

3. volatile變量規則:

對一個volatile域的寫,happens-before于任意后續對這個volatile域的讀。

volatile變量規則(Volatile Variable Rule)指的是對一個volatile域的寫操作 happens-before 于任意后續對這個volatile域的讀操作。這個規則確保了在多線程環境下,對volatile變量的可見性和順序性。

下面是一個簡單的Java程序示例,展示了volatile變量規則的應用:

public class VolatileVariableDemo {private volatile int number = 0;public void writeNumber() {number = 42; // 寫操作}public void readNumber() {System.out.println("Number: " + number); // 讀操作}public static void main(String[] args) {VolatileVariableDemo demo = new VolatileVariableDemo();Thread thread1 = new Thread(() -> {demo.writeNumber(); // 寫操作});Thread thread2 = new Thread(() -> {demo.readNumber(); // 讀操作});thread1.start();thread2.start();}
}

在上述示例中,我們創建了兩個線程:thread1和thread2,它們都會調用VolatileVariableDemo類中的writeNumber()和readNumber()方法。在這個類中,我們使用了一個volatile修飾的變量number。

根據volatile變量規則,線程1中的寫操作(number = 42)happens-before 線程2中的讀操作(System.out.println("Number: " + number))。

這意味著在線程1中,對number的寫操作一定會在線程2中對同一個number的讀操作之前發生。因此,我們可以確保在執行打印操作時,讀取到的number的值是線程1已經寫入的。

請注意,使用volatile修飾變量可以保證其在多線程環境下的可見性和順序性。這意味著對volatile變量的寫操作對其他線程是可見的,并且讀操作一定會讀取到最新的值。這是因為volatile變量規則確保了對volatile變量的寫 happens-before 于任意后續對這個volatile變量的讀。

4. 傳遞性:

如果A happens-before B,且B happens-before C,那么A happens-before C。

下面是一個新的示例,展示了happens-before關系的傳遞性:

public class TransitivityDemo {private int number = 0;private volatile boolean ready = false;public void writer() {number = 42; // 寫操作ready = true; // 寫操作}public void reader() {if (ready) { // 讀操作System.out.println("Number: " + number); // 讀操作}}public static void main(String[] args) {TransitivityDemo demo = new TransitivityDemo();Thread thread1 = new Thread(() -> {demo.writer(); // 寫操作});Thread thread2 = new Thread(() -> {demo.reader(); // 讀操作});thread1.start();thread2.start();}
}

在這個示例中,我們有一個TransitivityDemo類,其中包含一個number變量和一個ready變量。在writer()方法中,我們首先進行寫操作number = 42,然后進行寫操作ready = true。在reader()方法中,我們進行讀操作if (ready),如果ready為true,則進行讀操作System.out.println("Number: " + number)。

根據happens-before關系的傳遞性,線程1中的寫操作number = 42 happens-before 線程1中的寫操作ready = true。同時,線程1中的寫操作ready = true happens-before 線程2中的讀操作if (ready)。因此,我們可以得出結論,線程1中的寫操作number = 42 happens-before 線程2中的讀操作if (ready)。

由于happens-before關系的傳遞性,我們可以得出A happens-before C的結論,即線程1中的寫操作number = 42 happens-before 線程2中的讀操作System.out.println("Number: " + number)。

這個示例演示了happens-before關系的傳遞性,其中A happens-before B,B happens-before C,因此A happens-before C。

5. start()規則:

如果線程A執行操作ThreadB.start()(啟動線程B),那么A線程的 ThreadB.start()操作happens-before于線程B中的任意操作。

當線程A執行ThreadB.start()方法啟動線程B時,根據Java內存模型(Java Memory Model,JMM)中的start()規則,線程A的ThreadB.start()操作將happens-before于線程B中的任意操作。這意味著線程B中的任意操作都可以看到線程A在調用ThreadB.start()之前的操作。

下面是一個簡單的示例代碼來展示這個規則:

public class ThreadDemo {private static boolean ready = false;public static void main(String[] args) throws InterruptedException {ThreadB threadB = new ThreadB();Thread threadA = new Thread(() -> {System.out.println("Thread A is doing some work");ready = true;threadB.start(); // 線程A啟動線程B});threadA.start(); // 啟動線程AthreadA.join(); // 等待線程A執行完畢System.out.println("Thread B is ready? " + threadB.isReady());}static class ThreadB extends Thread {public boolean isReady() {return ready;}@Overridepublic void run() {System.out.println("Thread B is running");// 在這里可以看到線程A在調用ThreadB.start()之前的操作System.out.println("Thread B sees ready? " + ready);}}
}

在這個示例中,線程A首先會執行一些工作,并將ready屬性設置為true。然后,線程A調用threadB.start()來啟動線程B。在線程B的run()方法中,我們可以看到線程A在調用ThreadB.start()之前的操作,即輸出Thread B sees ready? true。

因此,根據start()規則,線程A的ThreadB.start()操作happens-before于線程B中的任意操作,確保了對ready屬性的修改對線程B可見。

運行結果如下:
happens-before

6. join()規則:

如果線程A執行操作ThreadB.join()并成功返回,那么線程B中的任意操作 happens-before于線程A從ThreadB.join()操作成功返回。

根據Java內存模型(Java Memory Model,JMM)中的join()規則,如果線程A執行操作ThreadB.join()并成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。這意味著線程A能夠看到線程B在join()之前的操作。

下面是一個簡單的示例代碼來展示這個規則:

public class ThreadDemo {private static boolean ready = false;public static void main(String[] args) throws InterruptedException {ThreadB threadB = new ThreadB();Thread threadA = new Thread(() -> {System.out.println("Thread A is doing some work");threadB.start(); // 線程A啟動線程Btry {threadB.join(); // 線程A等待線程B執行完畢} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread A sees ready? " + ready);});threadA.start(); // 啟動線程AthreadA.join(); // 等待線程A執行完畢System.out.println("Thread B is ready? " + threadB.isReady());}static class ThreadB extends Thread {public boolean isReady() {return ready;}@Overridepublic void run() {System.out.println("Thread B is running");ready = true; // 在這里修改ready屬性}}
}

在這個示例中,線程A首先會執行一些工作,并啟動線程B。然后,線程A調用threadB.join()來等待線程B執行完畢。在線程B的run()方法中,我們將ready屬性設置為true。

當線程A從threadB.join()成功返回后,它能夠看到線程B在join()之前的操作。因此,線程A輸出的Thread A sees ready? true將顯示線程B在join()之前將ready屬性設置為true。

因此,根據join()規則,線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回,確保了對ready屬性的修改對線程A可見。

運行結果:
happens-before

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

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

相關文章

【編程二三事】ES究竟是個啥?

在最近的項目中,總是或多或少接觸到了搜索的能力。而在這些項目之中,或多或少都離不開一個中間件 - ElasticSearch。 今天忙里偷閑,就來好好了解下這個中間件是用來干什么的。 ES是什么? ? ES全稱ElasticSearch,是個基于Lucen…

性能優化的重要性

性能優化的重要性 性能優化的重要性摘要引言注意事項代碼示例及注釋性能優化的重要性 性能優化的重要性在 Java 中的體現響應速度資源利用效率擴展性與可維護性并發性能合理的鎖策略線程安全的數據結構并發工具類的應用避免競態條件和死鎖 總結代碼示例 博主 默語帶您 Go to Ne…

一張圖看懂 USDT三種類型地址 Omni、ERC20、TRC20的區別

USDT是當前實用最廣泛,市值最高的穩定幣,它是中心化的公司Tether發行的。在今年的4月17日之前,市場上存在著2種不同類型的USDT。4月17日又多了一種波場TRC20協議發行的USDT,它們各自有什么區別呢?哪個轉賬最快到賬?哪…

谷歌推出首款量子彈性 FIDO2 安全密鑰

谷歌在本周二宣布推出首個量子彈性 FIDO2 安全密鑰,作為其 OpenSK 安全密鑰計劃的一部分。 Elie Bursztein和Fabian Kaczmarczyck表示:這一開源硬件優化的實現采用了一種新穎的ECC/Dilithium混合簽名模式,它結合了ECC抵御標準攻擊的安全性和…

[LeetCode]矩陣對角線元素的和

解題 思路 1: 循環,找到主對角線的下標和副對角線的下標,如果矩陣長或寬為奇數的時候,需要減去中間公共的那一個值,中間公共的那個數的下標為mat[mat.size()/2][mat.size()/2]副對角線的下標為 mat [i][mat.size()-i-1] class Solution { public:int diagonalSum(vector<ve…

JVM中判定對象是否回收的的方法

引用計數法 引用計數法是一種垃圾回收&#xff08;Garbage Collection&#xff09;算法&#xff0c;用于自動管理內存中的對象。在引用計數法中&#xff0c;每個對象都有一個關聯的引用計數器&#xff0c;用于記錄對該對象的引用數量。 當一個新的引用指向對象時&#xff0c;…

Hive底層數據存儲格式

前言 在大數據領域,Hive是一種常用的數據倉庫工具,用于管理和處理大規模數據集。Hive底層支持多種數據存儲格式,這些格式對于數據存儲、查詢性能和壓縮效率等方面有不同的優缺點。本文將介紹Hive底層的三種主要數據存儲格式:文本文件格式、Parquet格式和ORC格式。 一、三…

SpringBoot復習:(42)WebServerCustomizer的customize方法是在哪里被調用的?

ServletWebServletAutoConfiguration類定義如下&#xff1a; 可以看到其中通過Import注解導入了其內部類BeanPostProcessorRegister。 BeanPostProcessor中定義的registerBeanDefinition方法會被Spring容器調用。 registerBeanDefinitions方法調用了RegistrySyntheticBeanIf…

解決vue3前端獲取文件的絕對路徑問題

解決vue3前端獲取文件的絕對路徑問題 公司的項目是基于vue3的&#xff0c;由于需求需要前端獲取用戶選的文件的絕對路徑。但是瀏覽器處于安全策略無法獲取真實的文件路徑&#xff0c;只能拿到相對路徑或者是D:\fakepath\xxxx. 看了網上很多方法都很坑&#xff0c;明明沒拿到路…

vue基礎-vue監聽當前屏幕大小做不同的操作

文章目錄 前言一、代碼如下&#xff1a;總結 前言 在vue項目開發過程中&#xff0c;有個需求&#xff0c;就是當屏幕大于1024時&#xff0c;我們默認為PC模式。小于1024時&#xff0c;我們默認為H5模式。但是有的界面我們想在PC和H5上面展示不同的數據&#xff0c;請求不同的接…

Intellij IDEA SBT依賴分析插件

可分析模塊和傳遞依賴 安裝完插件后&#xff0c;由于IDEA BUG&#xff0c;會出現兩個分析按鈕&#xff0c;一個是gradle的&#xff0c;一般是后者是新安裝的sbt。 選擇需要分析的模塊 只需要在project/plugins.sbt中添加代碼&#xff0c;啟動官方分析插件addDependencyTreeP…

1281. 整數的各位積和之差

諸神緘默不語-個人CSDN博文目錄 力扣刷題筆記 文章目錄 1. 簡單粗暴的遍歷2. 其實也是遍歷&#xff0c;但是用Python內置函數只用寫一行 1. 簡單粗暴的遍歷 Python版&#xff1a; class Solution:def subtractProductAndSum(self, n: int) -> int:he0ji1while n>1:last…

redis 數據結構(一)

Redis 為什么那么快 redis是一種內存數據庫&#xff0c;所有的操作都是在內存中進行的&#xff0c;還有一種重要原因是&#xff1a;它的數據結構的設計對數據進行增刪查改操作很高效。 redis的數據結構是什么 redis數據結構是對redis鍵值對值的數據類型的底層的實現&#xff0c…

WordToPDF2.java

用Java將Word轉PDF 本例子測試了spire.doc.free-3.9.0.jar的包 <dependency><groupId> e-iceblue </groupId><artifactId>spire.doc.free</artifactId><version>3.9.0</version></dependency> package word;import com.spire.…

Java 動態代理

文章目錄 靜態代理Jdk動態代理cglib動態代理使用案例低配Mybatis低配Feign攔截器 附錄代碼 大家好&#xff0c;我是入錯行的bug貓。&#xff08;http://blog.csdn.net/qq_41399429&#xff0c;謝絕轉載&#xff09; 每天進步一點&#xff0c;今日再接再勵~ 動態代理在Java中有著…

“深入探索JVM內部機制:解密Java虛擬機“

標題&#xff1a;深入探索JVM內部機制&#xff1a;解密Java虛擬機 摘要&#xff1a;本篇博客將深入剖析Java虛擬機&#xff08;JVM&#xff09;的內部機制&#xff0c;包括類加載、內存管理、垃圾回收、即時編譯等關鍵組成部分。通過對JVM內部機制的解密&#xff0c;我們可以更…

團團代碼生成器V1.0:一鍵生成完整的CRUD功能(提供Gitee源碼)

前言&#xff1a;在日常開發的中&#xff0c;經常會需要重復寫一些基礎的增刪改查接口&#xff0c;雖說不難&#xff0c;但是會耗費我們一些時間&#xff0c;所以我自己開發了一套純SpringBoot實現的代碼生成器&#xff0c;可以為我們生成單條數據的增刪改查&#xff0c;還可以…

中遠麒麟堡壘機 SQL注入漏洞復現

0x01 產品簡介 中遠麒麟依托自身強大的研發能力,豐富的行業經驗&#xff0c;自主研發了新一代軟硬件一體化統一安全運維平臺一-iAudit 統一安全運維平臺。該產品支持對企業運維人員在運維過程中進行統一身份認證、統一授權、統一審計、統一監控&#xff0c;消除了傳統運維過程中…

實現Python腳本錄制功能

要實現Python腳本錄制功能&#xff0c;可以使用Python的內置模塊pyautogui和opencv。 首先&#xff0c;需要安裝這兩個模塊&#xff1a; pip install pyautogui opencv-python 然后&#xff0c;可以編寫以下代碼來實現腳本錄制功能&#xff1a; import cv2 import numpy as …

CentOS7.6安裝配置MySQL 5.7及常用命令匯總

一、MySQL安裝&#xff08;rpm安裝&#xff09; 1、檢查沒有安裝過mysql或mariadb rpm -qa | grep -i mysql rpm -qa | grep -i mariadb 返回空值的話&#xff0c;就說明沒有安裝 MySQL。注意&#xff1a;在新版本的CentOS7中&#xff0c;默認的數據庫已更新為了Mariadb&#…