java和内存交互,java内存模型-内存间交互操作
前言
本文是閱讀周志明大佬的《深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第3版)》第12章,12.3節(jié)Java內(nèi)存模型得來的讀書筆記。
閱讀告警😂😂😂,本文可能會(huì)有點(diǎn)枯燥,大部分內(nèi)容都是對(duì)書中內(nèi)容做一記錄。示例代碼可能會(huì)有不同。
一、內(nèi)存間交互操作
關(guān)于主內(nèi)存與工作內(nèi)存之間的具體交互協(xié)議,即一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存這一類的實(shí)現(xiàn)細(xì)節(jié),Java內(nèi)存模型中定義了8中操作,每一種操作都是原子的、不可再分的
lock(鎖定):作用于主內(nèi)存變量,把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)
unlock(解鎖):作用于主內(nèi)存變量,把一個(gè)處于鎖定狀態(tài)的變量釋放,釋放后的變量才可以被其他線程訪問
read(讀取):作用于主內(nèi)存的變量,把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便load操作使用
load(載入):作用于工作內(nèi)存的變量,把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中
use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作
assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作
store(存儲(chǔ)):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作使用
write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中
如圖所示,變量從主內(nèi)存到工作內(nèi)存,需要按順序執(zhí)行read和load,變量從工作內(nèi)存回到主內(nèi)存,按照store和write操作。java內(nèi)存模型只要求上述兩個(gè)操作必須按順序執(zhí)行,但不要求是連續(xù)執(zhí)行。
java內(nèi)存模型還規(guī)定了執(zhí)行上述8中基本操作時(shí)需要滿足以下規(guī)則:
不允許read和load,store和write操作之一單獨(dú)出現(xiàn),即不允許一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接受,或者工作內(nèi)存發(fā)起回寫了但主內(nèi)存不接受的情況。
不允許一個(gè)線程丟棄它最近的assign操作,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存
不允許一個(gè)線程無原因的(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中。
一個(gè)新的變量只能在主內(nèi)存中“誕生”,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load或assign)的變量,換句話說就是對(duì)一個(gè)變量實(shí)施user、store操作之前,必須先執(zhí)行assign和load操作
一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會(huì)被解鎖
如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,那將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前,需要重新執(zhí)行l(wèi)oad或assign操作以初始化變量的值
如果一個(gè)變量沒有被執(zhí)行l(wèi)ock操作,那么不允許對(duì)它執(zhí)行unlock操作,也不允許unlock一個(gè)被其他線程鎖定的變量
對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中
二、volatile變量的特殊規(guī)則
2.1 volatile變量的可見性
當(dāng)變量被定義為volatile之后, 保證此變量對(duì)所有線程的可見性。這里的“可見性”是指當(dāng)一條線程修改了這個(gè)變量的值,新值對(duì)于其他線程來說是可以立即得知的。普通變量并不能做到這一點(diǎn),因?yàn)槠胀ㄗ兞康闹翟诰€程間傳遞時(shí)均需要通過主內(nèi)存來完成。
那么是否可以理解為volatile變量在各個(gè)線程中是一致的,所以基于volatile變量的運(yùn)算在并發(fā)下都是線程安全的???
首先基于我們的經(jīng)驗(yàn),上述話的前半部分是對(duì)的,但是結(jié)論是不對(duì)的。為什么呢?我們可以先看一段例子:
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);
}
}
這段代碼發(fā)起了20個(gè)線程,每個(gè)線程對(duì)變量進(jìn)行100次自增操作,如果代碼能正確并發(fā)的話,最后輸出的結(jié)果是2000。但是我們運(yùn)行后,并不會(huì)獲得期望的結(jié)果,而且每次運(yùn)行的程序輸出的結(jié)構(gòu)都不一樣,是一個(gè)小于或者等于2000的數(shù)字,這是為什么呢?
問題在于race++操作,我們使用javap -v反編譯這段代碼后,發(fā)現(xiàn)race++是由4條字節(jié)碼構(gòu)成,如下圖所示
這樣,從字節(jié)碼層面就比較容易分析出并發(fā)失敗的原因了:當(dāng)getstatic指令把race讀取到棧頂操作時(shí),volatile關(guān)鍵字保證了race的值在此時(shí)是正確的,但是在執(zhí)行iconst_1、iadd這些指令時(shí),其他線程可能已經(jīng)把race的值改變了,而操作棧頂?shù)闹稻妥兂闪诉^期的數(shù)據(jù),所以putstatic指令執(zhí)行后就可能把較小的race同步回主內(nèi)存中。
2.2 禁止指令重排序優(yōu)化
普通變量?jī)H會(huì)保證在該方法的執(zhí)行過程中所有依賴賦值結(jié)果的地方都能得到正確的結(jié)果,而不能保證變量賦值操作的順序與程序代碼中的執(zhí)行順序一致。
Java內(nèi)存模型中對(duì)volatile變量定義的特殊規(guī)則如下:
線程對(duì)變量的load、read操作需要連續(xù)并且一起出現(xiàn)。即要求在工作內(nèi)存中,每次使用變量時(shí)都必須先從主內(nèi)存刷新最新的值,用于保證能看見其他線程對(duì)變量所做的修改。
線程對(duì)變量的store、write操作需要連續(xù)并且一起出現(xiàn)。即要求在工作內(nèi)存中,每次修改變量后都必須立刻同步回主內(nèi)存中,用于保證其他線程可以看到自己對(duì)變量V所做的修改。
假定動(dòng)作A是線程T對(duì)變量V實(shí)施的use或assign動(dòng)作,假定動(dòng)作F是和動(dòng)作A相關(guān)聯(lián)的load或store動(dòng)作,假定動(dòng)作P是和動(dòng)作F相應(yīng)的對(duì)變量V的read或write動(dòng)作;與此類似,假定動(dòng)作B是線程T對(duì)變量W實(shí)施的use或assign動(dòng)作,假定動(dòng)作G是和動(dòng)作B相關(guān)聯(lián)的load或store動(dòng)作,假定動(dòng)作Q是和動(dòng)作G相應(yīng)的對(duì)變量W的read或write動(dòng)作。如果A先于B,那么P先于Q。(這條規(guī)則要求volatile修飾的變量不會(huì)被指令重排序優(yōu)化,從而保證代碼的執(zhí)行順序與程序的順序相同。)
總結(jié)
以上是生活随笔為你收集整理的java和内存交互,java内存模型-内存间交互操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 静态代码块 多线程,Java多
- 下一篇: java旅游网站毕业论文,基于JAVA技