1.volatile與可見性java
都知道volatile能夠保證可見性,那麼究竟是如何保證的呢?git
這便於Happen-before原則有關,該原則的第三條規定:對一個volatile修飾的變量,寫操做要早於對這個變量的讀操做。具體步驟以下:github
A線程將共享變量讀進工做內存中,同時B線程也將共享變量讀進工做內存中。app
在A線程對共享變量修改後,會當即刷新到主內存,此時B線程的工做內存中的共享變量就會被設置無效,須要從主內存中從新讀取新值。反映到硬件上就是CPU的Cache line 置爲無效狀態。ide
這樣便保證了可見性,簡單而言,就是線程在對volatile修飾的變量修改且刷新到主內存以後,會使得其它線程的工做內存中的共享變量無效,須要從主內存中再此讀取。spa
2.volatile與有序性線程
都知道volatile能夠保證有序性,那麼究竟是如何保證的呢?excel
volatile保證有序性,比較直接,禁止JVM和處理器對volatile關鍵字修飾的變量進行指令重排序,但對於該變量以前或者以後的能夠任意排序,只要最終的結果與沒更改前的結果保持一致便可。blog
底層原理排序
被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了。