Java多线程之volatile详解
Java多線程之volatile詳解
目錄:
1. 什么是volatile?
答:volatile是java虛擬機提供的輕量級的同步機制(可以理解成乞丐版的synchronized)
特性有:
理解volatile特性之一保證可見性之前要先理解什么是JMM內存模型的可見性
2. JMM內存模型之可見性
JMM(Java內存模型Java Memory Model,簡稱JMM)本身是一種抽象的概念并不真實存在,它描述的是一組規則或規范,通過這組規范定義了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式。
JMM關于同步的規定:
由于JVM運行程序的實體是線程,而每個線程創建時JVM都會為其創建一個工作內存(有些地方稱為??臻g),工作內存是每個線程的私有數據區域,而Java內存模型中規定所有變量都存儲在主內存,主內存是共享內存區域,所有線程都可以訪問,但線程對變量 的操作(讀取賦值等)必須在工作內存中進行,首先要將變量從主內存拷貝的自己的工作內存空間,然后對變量進行操作,操作完成 后再將變量寫回主內存,不能直接操作主內存中的變量,各個線程中的工作內存中存儲著主內存中的變量副本拷貝,因此不同的線
程間無法去訪問對方的工作內存,線程間的通信(傳值)必須通過主內存來完成,其簡要訪問過程如下圖:
4.圖解:
即創建student對象age=25,每個線程自己的工作內存都會拷貝一份age = 25,當線程t1修改age=37后,需要把age=37寫回主內存,然后主內存向其他線程分發最新的值。
volatile保證可見性特性也是如此
volatile三大特性之一:保證可見性
1. 結合代碼理解volatile的可見性
代碼
import java.util.concurrent.TimeUnit;class MyData{int number = 0;public void addTo60(){this.number = 60;} }public class VolatileDemo {public static void main(String[] args) {MyData myData = new MyData();new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t come in");try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}myData.addTo60();System.out.println(Thread.currentThread().getName()+"\t update number value:"+myData.number);},"AAA").start();//第2個線程就是我們的main線程//如果number==0,那么一直在死循環,下面的輸出打印不出來,//如果打印了,就是main線程感知到了number已經從0變為了60,可見性被觸發while (myData.number==0){// main線程就一直在這里等待循環,直到number值不再為零。}System.out.println(Thread.currentThread().getName()+"\t mission is over");} }編譯結果:
AAA線程已經把myData.number從0賦值為60,并且寫回了主內存,但是對main線程不可見。所以main線程一直在傻傻的等while(myData.number==0),但實際真實值number=60了,
2. 當我們在number添加volatile修飾符,即volatile int number = 0;
代碼:
import java.util.concurrent.TimeUnit;class MyData{//volatile 增強了主內存和各線程之間的可見性,只有有一個線程改了主內存的值, // 其他線程馬上會收到通知。迅速獲得最新值。volatile int number = 0;public void addTo60(){this.number = 60;} }public class VolatileDemo {public static void main(String[] args) {MyData myData = new MyData();new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t come in");try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}myData.addTo60();System.out.println(Thread.currentThread().getName()+"\t update number value:"+myData.number);},"AAA").start();//第2個線程就是我們的main線程//如果number==0,那么一直在死循環,下面的輸出打印不出來,//如果打印了,就是main線程感知到了number已經從0變為了60,可見性被觸發while (myData.number==0){// main線程就一直在這里等待循環,直到number值不再為零。}System.out.println(Thread.currentThread().getName()+"\t mission is over" +", main get number value:"+myData.number);} }編譯結果:
4. volatile三大特性之二:不保證原子性
1.首先要知道原子性指的是什么意思?
- 不可分割,完整性,即某個線程正在做某個具體業務時,中間不可以被加塞或者被分割。
- 需要整體完整。要么同時成功,要么同時失敗。
2.通過代碼驗證volatile不保證原子性
代碼:
class MyData {//volatile 增強了主內存和各線程之間的可見性,只有有一個線程改了主內存的值, // 其他線程馬上會收到通知。迅速獲得最新值。volatile int number = 0;public void addTo60() {this.number = 60;}//請注意,此時number前面是加了volatile關鍵字修飾的,volatile不保證原子性。public void addPlusPlus() {number++;} }public class VolatileDemo {public static void main(String[] args) {MyData myData = new MyData();for (int i = 1; i <= 20; i++) {new Thread(() -> {for (int j = 1; j <= 1000; j++) {myData.addPlusPlus();}}, String.valueOf(i)).start();}//需要等待上面20個線程全部計算完成之后,再用main線程取得最終的結果值看是多少// >2 是因為后臺有兩個線程,1是main線程,2是GC線程。// 能最好的控制時間while (Thread.activeCount()>2){Thread.yield(); //禮讓線程,退不執行。}System.out.println(Thread.currentThread().getName()+"\t finally number value:"+myData.number);} }編譯結果:
多次測試,都沒有20000,說明有值丟失,即不保證原子性
3.如何解決volatile不保證原子性的問題?
我們可以用java.util.concurrent.atomic包下的 AtomicInteger解決這個問題
具體使用如下:
編譯結果:
4.關于volatile數字丟失的簡單原理:
上圖解釋:
- 比如拿回自己工作空間的時候都是3,+1后寫回去的時候,正好被別的線程捷足先登,只能掛起,已經有線程把4寫了回去,等再喚醒的時候再把4寫回去就會造成丟值
5. number++在多線程下是不安全的,為什么不用synchronized?
因為synchronized是重鎖,有更合適的就用更合適的,殺雞焉用牛刀。
5. volatile三大特性之三: 禁止指令重排
1. 說指令重排之前,我們要知道什么是有序性?
可能會出現問題,如下:
2. volatile特性之三:禁止指令重排
3.內存屏障
- 保證特定操作的執行順序,
- 保證某些變量的內存可見性(利用該特性實現volatile的內存可見性)。
和這條MemoryBarrier指令重排序,也就是說通過插入內存屏障禁止在內存屏障前后的指令執行重排序優化。內存屏障另外一個作
用是強制刷出各種CPU的緩存數據,因此任何CPU上的線程都能讀取到這些數據的最新版本。
6. 小結
工作內存與主內存同步延遲現象導致的可見性問題
解:可以使用Synchronized或volatile關鍵字解決,它們都可以使一個線程修改后的變量立即對其他線程可見。
對于指令重排導致的可見性問題和有序性問題
解:可以利用volatile關鍵字解決,因為volatile的另外一個作用就是禁止重排序優化。
總結
以上是生活随笔為你收集整理的Java多线程之volatile详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IDEA设置自定义代码模板
- 下一篇: Java多线程之单例模式在多线程环境下的