日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java和内存交互,java内存模型-内存间交互操作

發布時間:2023/11/27 编程问答 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java和内存交互,java内存模型-内存间交互操作 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

本文是閱讀周志明大佬的《深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版)》第12章,12.3節Java內存模型得來的讀書筆記。

閱讀告警😂😂😂,本文可能會有點枯燥,大部分內容都是對書中內容做一記錄。示例代碼可能會有不同。

一、內存間交互操作

關于主內存與工作內存之間的具體交互協議,即一個變量如何從主內存拷貝到工作內存、如何從工作內存同步回主內存這一類的實現細節,Java內存模型中定義了8中操作,每一種操作都是原子的、不可再分的

lock(鎖定):作用于主內存變量,把一個變量標識為一條線程獨占的狀態

unlock(解鎖):作用于主內存變量,把一個處于鎖定狀態的變量釋放,釋放后的變量才可以被其他線程訪問

read(讀取):作用于主內存的變量,把一個變量的值從主內存傳輸到線程的工作內存中,以便load操作使用

load(載入):作用于工作內存的變量,把read操作從主內存中得到的變量值放入工作內存的變量副本中

use(使用):作用于工作內存的變量,它把工作內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值的字節碼指令時將會執行這個操作

assign(賦值):作用于工作內存的變量,它把一個從執行引擎接收的值賦給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作

store(存儲):作用于工作內存的變量,它把工作內存中一個變量的值傳送到主內存中,以便隨后的write操作使用

write(寫入):作用于主內存的變量,它把store操作從工作內存中得到的變量的值放入主內存的變量中

如圖所示,變量從主內存到工作內存,需要按順序執行read和load,變量從工作內存回到主內存,按照store和write操作。java內存模型只要求上述兩個操作必須按順序執行,但不要求是連續執行。

java內存模型還規定了執行上述8中基本操作時需要滿足以下規則:

不允許read和load,store和write操作之一單獨出現,即不允許一個變量從主內存讀取了但工作內存不接受,或者工作內存發起回寫了但主內存不接受的情況。

不允許一個線程丟棄它最近的assign操作,即變量在工作內存中改變了之后必須把該變化同步回主內存

不允許一個線程無原因的(沒有發生過任何assign操作)把數據從線程的工作內存同步回主內存中。

一個新的變量只能在主內存中“誕生”,不允許在工作內存中直接使用一個未被初始化(load或assign)的變量,換句話說就是對一個變量實施user、store操作之前,必須先執行assign和load操作

一個變量在同一個時刻只允許一條線程對其進行lock操作,但lock操作可以被同一條線程重復執行多次,多次執行lock后,只有執行相同次數的unlock操作,變量才會被解鎖

如果對一個變量執行lock操作,那將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新執行load或assign操作以初始化變量的值

如果一個變量沒有被執行lock操作,那么不允許對它執行unlock操作,也不允許unlock一個被其他線程鎖定的變量

對一個變量執行unlock操作之前,必須先把此變量同步回主內存中

二、volatile變量的特殊規則

2.1 volatile變量的可見性

當變量被定義為volatile之后, 保證此變量對所有線程的可見性。這里的“可見性”是指當一條線程修改了這個變量的值,新值對于其他線程來說是可以立即得知的。普通變量并不能做到這一點,因為普通變量的值在線程間傳遞時均需要通過主內存來完成。

那么是否可以理解為volatile變量在各個線程中是一致的,所以基于volatile變量的運算在并發下都是線程安全的???

首先基于我們的經驗,上述話的前半部分是對的,但是結論是不對的。為什么呢?我們可以先看一段例子:

public class VolatileTest {

public static volatile int race = 0;

public static void increase() {

race++;

}

private static final int THREADS_COUNT = 20;

public static void main(String[] args) {

CountDownLatch countDownLatch = new CountDownLatch(THREADS_COUNT);

ExecutorService service = new ThreadPoolExecutor(5, 8,

1000, TimeUnit.SECONDS,

new LinkedBlockingQueue<>(1024), new ThreadPoolExecutor.AbortPolicy());

for (int i = 0; i < THREADS_COUNT; i++) {

service.execute(() -> {

for (int i1 = 0; i1 < 100; i1++) {

increase();

}

countDownLatch.countDown();

});

}

try {

countDownLatch.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

service.shutdown();

System.out.println(race);

}

}

這段代碼發起了20個線程,每個線程對變量進行100次自增操作,如果代碼能正確并發的話,最后輸出的結果是2000。但是我們運行后,并不會獲得期望的結果,而且每次運行的程序輸出的結構都不一樣,是一個小于或者等于2000的數字,這是為什么呢?

問題在于race++操作,我們使用javap -v反編譯這段代碼后,發現race++是由4條字節碼構成,如下圖所示

這樣,從字節碼層面就比較容易分析出并發失敗的原因了:當getstatic指令把race讀取到棧頂操作時,volatile關鍵字保證了race的值在此時是正確的,但是在執行iconst_1、iadd這些指令時,其他線程可能已經把race的值改變了,而操作棧頂的值就變成了過期的數據,所以putstatic指令執行后就可能把較小的race同步回主內存中。

2.2 禁止指令重排序優化

普通變量僅會保證在該方法的執行過程中所有依賴賦值結果的地方都能得到正確的結果,而不能保證變量賦值操作的順序與程序代碼中的執行順序一致。

Java內存模型中對volatile變量定義的特殊規則如下:

線程對變量的load、read操作需要連續并且一起出現。即要求在工作內存中,每次使用變量時都必須先從主內存刷新最新的值,用于保證能看見其他線程對變量所做的修改。

線程對變量的store、write操作需要連續并且一起出現。即要求在工作內存中,每次修改變量后都必須立刻同步回主內存中,用于保證其他線程可以看到自己對變量V所做的修改。

假定動作A是線程T對變量V實施的use或assign動作,假定動作F是和動作A相關聯的load或store動作,假定動作P是和動作F相應的對變量V的read或write動作;與此類似,假定動作B是線程T對變量W實施的use或assign動作,假定動作G是和動作B相關聯的load或store動作,假定動作Q是和動作G相應的對變量W的read或write動作。如果A先于B,那么P先于Q。(這條規則要求volatile修飾的變量不會被指令重排序優化,從而保證代碼的執行順序與程序的順序相同。)

總結

以上是生活随笔為你收集整理的java和内存交互,java内存模型-内存间交互操作的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

歡迎分享!

轉載請說明來源于"生活随笔",并保留原作者的名字。

本文地址:java和内存交互,java内存模型-内存间交互操作