Java-JUC(一):volatile引入
問題背景:
volatile是為了解決內(nèi)存可見性而生的,什么是內(nèi)存不可見性呢?
以下邊的代碼為例:
package com.dx.juc;public class VoltileTest {public static void main(String[] args) {MyThread thread=new MyThread();thread.start();while (true){if(thread.flag){System.out.println("thread flag is true");break;}}System.out.println("complete");} }class MyThread extends Thread {public boolean flag = false;@Overridepublic void run() {try {Thread.sleep(200);flag = true;System.out.println("flag is changed;" + flag);} catch (InterruptedException ex) {ex.printStackTrace();}} }在線程thread開始執(zhí)行的過程中會吧thread.flag屬性值修改為true,一般情況下來說,main線程在while(true)循環(huán)內(nèi)部是可以檢測到thread.flag被修改了,而且我們希望是這樣子。但是程序運行起來的時候會發(fā)現(xiàn)flag在線程thread中被修改后,main線程并不能讀取到被修改的值。
輸出結(jié)果為:
此時,就是main一直在執(zhí)行while(true)循環(huán)操作。
出現(xiàn)問題的原因?
?
原因:
1)thread線程修改flag值時間晚于main獲取(拷貝)flag值(到main緩存)的時間;
2)同時main線程中讀取flag數(shù)據(jù)是從main線程的緩存中讀取,而不是直接從主存中讀取。
或用更簡潔的話來描述:
兩個線程操作共享數(shù)據(jù)時,彼此不可見,線程可見性導(dǎo)致的問題。
那么為什么要使用緩存?
- Register
寄存器是CPU的內(nèi)部組成單元,是CPU運算時取指令和數(shù)據(jù)的地方,速度很快,寄存器可以用來暫存指令、數(shù)據(jù)和地址。在CPU中,通常有通用寄存器,如指令寄存器IR;特殊功能寄存器,如程序計數(shù)器PC、sp等
- 寄存器的工作方式很簡單,只有兩步:(1)找到相關(guān)的位,(2)讀取這些位。
- Cache
緩存即就是用于暫時存放內(nèi)存中的數(shù)據(jù),若果寄存器要取內(nèi)存中的一部分數(shù)據(jù)時,可直接從緩存中取到,這樣可以調(diào)高速度。高速緩存是內(nèi)存的部分拷貝。
- 內(nèi)存的工作方式就要復(fù)雜得多:
(1)找到數(shù)據(jù)的指針。(指針可能存放在寄存器內(nèi),所以這一步就已經(jīng)包括寄存器的全部工作了。)
(2)將指針?biāo)屯鶅?nèi)存管理單元(MMU),由MMU將虛擬的內(nèi)存地址翻譯成實際的物理地址。
(3)將物理地址送往內(nèi)存控制器(memory controller),由內(nèi)存控制器找出該地址在哪一根內(nèi)存插槽(bank)上。
(4)確定數(shù)據(jù)在哪一個內(nèi)存塊(chunk)上,從該塊讀取數(shù)據(jù)。
(5)數(shù)據(jù)先送回內(nèi)存控制器,再送回CPU,然后開始使用。
內(nèi)存的工作流程比寄存器多出許多步。每一步都會產(chǎn)生延遲,累積起來就使得內(nèi)存比寄存器慢得多。
為了緩解寄存器與內(nèi)存之間的巨大速度差異,硬件設(shè)計師做出了許多努力,包括在CPU內(nèi)部設(shè)置緩存、優(yōu)化CPU工作方式,盡量一次性從內(nèi)存讀取指令所要用到的全部數(shù)據(jù)等等。
?如何解決內(nèi)存不可見的問題?使用volatile
?使用java中的volatile實現(xiàn)內(nèi)存可見性
package com.dx.juc;public class VoltileTest {public static void main(String[] args) {MyThread thread = new MyThread();thread.start();while (true) {if (thread.isFlag()) {System.out.println("thread flag is true");break;}}System.out.println("complete");} }class MyThread extends Thread {private volatile boolean flag = false;@Overridepublic void run() {try {Thread.sleep(200);setFlag(true) ;System.out.println("flag is changed;" + isFlag());} catch (InterruptedException ex) {ex.printStackTrace();}}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;} }?使用了volatile后確實可以保證了內(nèi)存可見性,當(dāng)thread線程修改了flag的值時,會先把thread線程緩存中的值修改,立即把thread線程緩存中修改的值刷新到主存中,同時引用了被volatile修飾的變量所在的線程對應(yīng)緩存(清空)失效,此時main線程在while循環(huán)處理是檢測到緩存中無該變量值,則要從主存中獲取flag對象,因此,確保了數(shù)據(jù)的可見性。
用volatile修飾之后帶來的影響:
第一:使用volatile關(guān)鍵字會強制將修改的值立即寫入主存;
第二:使用volatile關(guān)鍵字的話,當(dāng)線程thread進行修改時,會導(dǎo)致線程main的工作內(nèi)存中緩存變量flag的緩存行無效(反映到硬件層的話,就是CPU的L1或者L2緩存中對應(yīng)的緩存行無效);
第三:由于線程main的工作內(nèi)存中緩存變量flag的緩存行無效,所以線程main再次讀取變量flag的值時會去主存讀取。
那么在線程thread修改flag值時(當(dāng)然這里包括2個操作,修改線程thread工作內(nèi)存中的值,然后將修改后的值寫入內(nèi)存),會使得線程main的工作內(nèi)存中緩存變量flag的緩存行無效,然后線程main讀取時,發(fā)現(xiàn)自己的緩存行無效,它會等待緩存行對應(yīng)的主存地址被更新之后,然后去對應(yīng)的主存讀取最新的值。
那么線程main讀取到的就是最新的正確的值。?
轉(zhuǎn)載于:https://www.cnblogs.com/yy3b2007com/p/8890673.html
總結(jié)
以上是生活随笔為你收集整理的Java-JUC(一):volatile引入的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ueditor百度编辑器中,多图上传后,
- 下一篇: 数据结构之图的创建(邻接表)