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