您好,登錄后才能下訂單哦!
1.volatile與可見性
都知道volatile可以保證可見性,那么到底是如何保證的呢?
這便于Happen-before原則有關,該原則的第三條規定:對一個volatile修飾的變量,寫操作要早于對這個變量的讀操作。具體步驟如下:
A線程將共享變量讀進工作內存中,同時B線程也將共享變量讀進工作內存中。
在A線程對共享變量修改后,會立即刷新到主內存,此時B線程的工作內存中的共享變量就會被設置無效,需要從主內存中重新讀取新值。反映到硬件上就是CPU的Cache line 置為無效狀態。
這樣便保證了可見性,簡單而言,就是線程在對volatile修飾的變量修改且刷新到主內存之后,會使得其它線程的工作內存中的共享變量無效,需要從主內存中再此讀取。
2.volatile與有序性
都知道volatile可以保證有序性,那么到底是如何保證的呢?
volatile保證有序性,比較直接,禁止JVM和處理器對volatile關鍵字修飾的變量進行指令重排序,但對于該變量之前或者之后的可以任意排序,只要最終的結果與沒更改前的結果保持一致即可。
底層原理
被volatile修飾的變量在底層會加一個“lock:”的前綴,帶"lock"前綴的指令相當于一個內存屏障,這恰恰是保證可見性與有序性的關鍵,該屏障的作用主要有一下幾點:
指令重排時,屏障前的代碼不能重排到屏障后,屏障后的也不能重排到屏障前。
執行到內存屏障時,確保前面的代碼都已經執行完畢,且執行結果是對屏障后的代碼可見的。
強制將工作內存中的變量刷新到主內存。
其它線程的工作內存的變量會設置無效,需要重現從主內存中讀取。
3.volatile與原子性
都知道volatile不能保證原子性,那么為何不能保證原子性呢?
代碼演示:
package com.github.excellent01;
import java.util.concurrent.CountDownLatch;
/**
* @auther plg
* @date 2019/5/19 9:37
*/
public class TestVolatile implements Runnable {
private volatile Integer num = 0;
private static CountDownLatch latch = new CountDownLatch(10);
@Override
public void run() {
for(int i = 0; i < 1000; i++){
num++;
}
latch.countDown();
}
public Integer getNum() {
return num;
}
public static void main(String[] args) throws InterruptedException {
TestVolatile test = new TestVolatile();
for(int i = 0; i < 10; i++){
new Thread(test).start();
}
latch.await();
System.out.println(test.getNum());
}
}
啟動10個線程,每個線程對共享變量num,加1000次,當所有的線程執行完畢之后,打印輸出num 的最終結果。
很少有10000的這便是因為volatile不能保證原子性造成的。
原因分析:
num++的操作由三步組成:
從主內存將num讀進工作內存中
在工作內存中進行加一
加一完成后,寫回主內存。
雖然這三步都是原子操作,但合起來不是原子操作,每一步執行的過程中都有可能被打斷。
假設此時num的值為10,線程A將變量讀進自己的工作內存中,此時發生了CPU切換,B也將num讀進自己的工作內存,此時值也是10.B線程在自己的工作內存中對num的值進行修改,變成了11,但此時還沒有刷新到主內存,因此A線程還不知道num的值已經發生了改變,之前所說的,對volatile變量修改后,其它線程會立即得知,前提也是要先刷新到主內存中,這時,其它線程才會將自己工作中的共享變量的值設為無效。因為沒有刷新到主內存,因此A傻傻的不知道,在10的基礎上加一,因此最終雖然兩個線程都進行了加一操作,但最終的結果只加了一次。
這便是為什么volatile不能保證原子性。
volatile的使用場景
根據volatile的特點,保證有序性,可見性,不能保證原子性,因此volatile可以用于那些不需要原子性,或者說原子性已經得到保障的場合:
代碼演示
volatile boolean shutdownRequested
public void shutdown() {
shutdownRequested = true;
}
public void work() {
while(shutdownRequested) {
//do stuff
}
}
只要線程對shutdownRequested進行修改,執行work的線程會立即看到,因此會立即停止下來,如果不加volatile的話,它每次去工作內存中讀取數據一直是個true,一直執行,都不知道別人已經讓它停了。
代碼演示:無錫看婦科哪里好 http://www.xasgfk.cn/
package com.github.excellent;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 啟動線程會被阻塞,flag 從內存讀入,會存入寄存器中,下次直接從寄存器取值
* 因此值一直是false
* 即使別的線程已經將值更改了,它也不知道
* 加volatile即可。也可以加鎖,只要保證內存可見性即可
* @auther plg
* @date 2019/5/2 22:40
*/
public class Testvolatile {
public static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
for(;;) {
System.out.println(flag);
}
});
Thread thread2 = new Thread(()->{
for(;;){
flag = true;
}
});
thread1.start();
Thread.sleep(1000);
thread2.start();
}
}
執行結果:
加一個volatile就ok了。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。