摘要:比如用修飾的變量,就會確保變量在修改時,其它線程是可見的。。多核環(huán)境中,多個線程分別在不同的中運行,就意味著,多個線程都有可能將變量拷貝到當(dāng)前運行的里。當(dāng)線程讀取變量時,它將能看見被線程寫入的東西。
概述volatile是用來標(biāo)記一個JAVA變量存儲在主內(nèi)存(main memory)中,多線程讀寫volatile變量會先從高速緩存中讀取,但是寫入的時候會立即通過內(nèi)存總線刷到主存,同時內(nèi)存總線中會對這個變量進行監(jiān)聽,當(dāng)發(fā)現(xiàn)數(shù)據(jù)變動時,會主動將該變量的CPU Cache置為失效。確切的說:每次寫操作volatile變量時,將直接將主內(nèi)存(main memory)中最新的值讀取到當(dāng)前Cache操作
可見性: 是指線程之間數(shù)據(jù)可見共享,一個線程修改的狀態(tài)對另一個線程是可見的。比如:用volatile修飾的變量,就會確保變量在修改時,其它線程是可見的。。
在多線程中,對非volatile變量進行操作的時候,出于對性能的考慮,當(dāng)對這些變量進行數(shù)據(jù)操作時,線程可能會從主內(nèi)存里拷貝變量到CPU Cache中去。多核CPU環(huán)境中,多個線程分別在不同的CPU中運行,就意味著,多個線程都有可能將變量拷貝到當(dāng)前運行的CPU Cache里。
如下圖所示(多線程數(shù)據(jù)模型):
示例 - 非Volatilepublic class NotSharedObject { private static int COUNTER = 0; private static final int MAX_LIMIT = 5; public static void main(String[] args) { new Thread(() -> { int localValue = COUNTER; while (localValue < MAX_LIMIT) { if (localValue != COUNTER) { System.out.printf("[線程] - [%s] - [%d] ", Thread.currentThread().getName(), COUNTER); localValue = COUNTER; } } }, "READER").start(); new Thread(() -> { int localValue = COUNTER; while (COUNTER < MAX_LIMIT) { System.out.printf("[線程] - [%s] - [%d] ", Thread.currentThread().getName(), ++localValue); COUNTER = localValue; } }, "UPDATER").start(); } }
[線程] - [UPDATER] - [1] [線程] - [UPDATER] - [2] [線程] - [UPDATER] - [3] [線程] - [UPDATER] - [4] [線程] - [UPDATER] - [5]
結(jié)果表明,UPDATE線程雖修改數(shù)據(jù),但是READER線程并未監(jiān)聽到數(shù)據(jù)的變動,當(dāng)前線程操作的是當(dāng)前CPU Cache里的數(shù)據(jù),而不是從main memory獲取的。
couner 的變量未使用volatile關(guān)鍵字修飾,即JVM無法保證有效的將CPU Cache的內(nèi)容寫入主存中。意味著 counter 變量在CPU Cache中的值可能會與主存中的值不一樣。
如下圖所示(無Volatile):
示例 - Volatileprivate static volatile int COUNTER = 0; [線程] - [UPDATER] - [1] [線程] - [UPDATER] - [2] [線程] - [READER] - [1] [線程] - [UPDATER] - [3] [線程] - [UPDATER] - [4] [線程] - [UPDATER] - [5] [線程] - [READER] - [3]
結(jié)果表明,volatile修飾后的變量并不會達到Lock的效果,它只會保證線程可見性,但不保證原子性,在讀取volatile變量和寫入它的新值時,由于操作耗時較短,就會產(chǎn)生 競爭條件:多個線程可能會讀取到volatile變量的相同值,然后產(chǎn)生新值并寫入主內(nèi)存,這樣將會覆蓋互相的值。(有興趣的可以在創(chuàng)建一個UPDATE線程測試效果)
如下圖所示(Volatile):
Happens-Before 原則從Java5之后volatile關(guān)鍵字不僅能用于保證變量從主存中進行讀寫操作,同時還遵循Happens-Before原則,下文將會描述存在于volatile中的一些細節(jié),想深入的可以自行谷歌 happens-before relationship或者訪問提供的幾個鏈接
參考文獻(1):https://en.wikipedia.org/wiki/Happened-before
參考文獻(2):http://preshing.com/20130702/the-happens-before-relation/
參考文獻(3):http://www.importnew.com/17149.html
如果T1線程寫入了一個volatile變量然后T2線程讀取該變量,那么T1線程寫之前對其可見的所有變量,T2線程讀取該volatile之后也會對其可見。
禁止JVM指令重排優(yōu)化,一旦被volatile修飾的變量,賦值后多執(zhí)行了一個load addl $0x0, (%esp)操作,相當(dāng)于多了一個內(nèi)存屏障(指令重排序時不能把后面的指令重排序到內(nèi)存屏障之前的位置),單核CPU訪問內(nèi)存時,并不需要內(nèi)存屏障;
看看下面這個示例:
T1線程: Object obj; volatile boolean init; ---------T1線程------------ obj = createObj() 1; init = true; 2; ---------T2線程------------ while(!init){ sleep(); } useTheObj(obj);
被volatile修飾過的變量 init在寫操作之前,創(chuàng)建了非volatile變量的obj,因而T1線程在寫入init后,會將obj也寫入主內(nèi)存中去。
由于T2線程啟動的時候讀取被volatile修飾過的init,因而變量 init 和變量 obj 都會被寫入T2線程所使用的CPU緩存中去。當(dāng)T2線程讀取 obj 變量時,它將能看見被T1線程寫入的東西。
總結(jié)適用場景線程可見,狀態(tài)量標(biāo)記
volatile boolean start = true; while(start){ // } void close(){ start = false; }
屏障前后一致性,禁止指令重排
- 說點什么全文代碼:https://gitee.com/battcn/battcn-concurent/tree/master/Chapter1-1/battcn-thread/src/main/java/com/battcn/chapter13
個人QQ:1837307557
battcn開源群(適合新手):391619659
微信公眾號:battcn(歡迎調(diào)戲)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/67826.html
摘要:但是的語義不足以確保遞增操作的原子性,在多線程的情況下,線程不一定是安全的。檢查某個狀態(tài)標(biāo)記,以判斷是否退出循環(huán)某個方法這邊和用普通的變量的區(qū)別是,在多線程的情況下,取到后,的值被改變了,判斷會不正確。 多線程為什么是不安全的 這邊簡單的講述一下,參考java并發(fā)編程學(xué)習(xí)之synchronize(一) 當(dāng)線程A和線程B同時進入num = num + value; 線程A會把num的值...
摘要:文本將介紹兩種可以優(yōu)雅的終止線程的方式第一種在多線程模式中有一種叫兩步終止的模式可以優(yōu)雅的終止線程,這種模式采用了兩個步驟來終止線程,所以叫兩步終止模式。 Java中原來在Thread中提供了stop()方法來終止線程,但這個方法是不安全的,所以一般不建議使用。文本將介紹兩種可以優(yōu)雅的終止線程的方式... 第一種 在JAVA《Java多線程模式》中有一種叫Two-Phase Term...
摘要:三關(guān)鍵字能保證原子性嗎并發(fā)編程藝術(shù)這本書上說保證但是在自增操作非原子操作上不保證,多線程編程核心藝術(shù)這本書說不保證。多線程訪問關(guān)鍵字不會發(fā)生阻塞,而關(guān)鍵字可能會發(fā)生阻塞關(guān)鍵字能保證數(shù)據(jù)的可見性,但不能保證數(shù)據(jù)的原子性。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Java多線程學(xué)習(xí)(二)synchronized關(guān)鍵字(1) java多線程學(xué)習(xí)(二)synchroniz...
摘要:學(xué)習(xí)完多線程之后可以通過下面這些問題檢測自己是否掌握,下面這些問題的答案以及常見多線程知識點的總結(jié)在這里??蛇x數(shù)據(jù)結(jié)構(gòu)與算法如果你想進入大廠的話,我推薦你在學(xué)習(xí)完基礎(chǔ)或者多線程之后,就開始每天抽出一點時間來學(xué)習(xí)算法和數(shù)據(jù)結(jié)構(gòu)。 我自己總結(jié)的Java學(xué)習(xí)的系統(tǒng)知識點以及面試問題,已經(jīng)開源,目前已經(jīng) 35k+ Star。會一直完善下去,歡迎建議和指導(dǎo),同時也歡迎Star: https://...
摘要:每個對象只有一個鎖與之相關(guān)聯(lián)。實現(xiàn)同步則是以系統(tǒng)開銷作為代價,甚至可能造成死鎖,所以盡量避免濫用。這種機制確保了同一時刻該類實例,所有聲明為的函數(shù)中只有一個方法處于可執(zhí)行狀態(tài),從而有效避免了類成員變量訪問沖突。 synchronized是JAVA語言的一個關(guān)鍵字,使用 synchronized 來修飾方法或代碼塊的時候,能夠保證多個線程中最多只有一個線程執(zhí)行該段代碼 ... 概述 ...
閱讀 3000·2021-10-19 11:46
閱讀 991·2021-08-03 14:03
閱讀 2952·2021-06-11 18:08
閱讀 2923·2019-08-29 13:52
閱讀 2778·2019-08-29 12:49
閱讀 497·2019-08-26 13:56
閱讀 937·2019-08-26 13:41
閱讀 861·2019-08-26 13:35