多執行緒(六):volatile
一:執行緒無法終止
public class MyThread extends Thread {
private static boolean flag = true;
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
while (flag) {
}
System.out.println("執行緒停止了");
}
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName());
new MyThread().start();
Thread.sleep(1000);
flag = false;
}
}
為什麼執行緒無法終止?
每個執行緒都有自己的工作記憶體,執行緒對變數的所有操作都必須在自己的工作記憶體中進行,而不能直接對主記憶體進行操作,並且每個執行緒不能訪問其他執行緒的工作記憶體。當執行緒訪問全域性變數的時候,會將全域性變數拷貝一份放在自己的工作記憶體當中。主執行緒先執行,會將全域性變數falg = true拷貝到自己的工作記憶體中,然後執行Thread-0執行緒,同樣Thread-0也會將全域性變數拷貝到自己的工作記憶體中,Thread-0 讀取到自己的工作記憶體中的flag為true會一直迴圈,當CPU切換到主執行緒main時將flag=true的值修改為false,此時main執行緒的工作記憶體中的falg=false,但是main執行緒對全域性變數的更改不會立即同步到全域性變數的值,即雖然main的值為false,但全域性變數的值扔為true,main修改了值很快就結束了,mian執行緒對flag的修改並沒有影響到Thread-0執行緒工作記憶體中的值,Thread-0工作記憶體的值一直為true,一直迴圈,不會結束。
二:執行緒終止結束
public class MyThread extends Thread {
private static volatile boolean flag = true;
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
while (flag) {
}
System.out.println("執行緒停止了");
}
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName());
new MyThread().start();
Thread.sleep(1000);
flag = false;
}
}
- main執行緒將全域性變數flag=true拷貝到自己的工作記憶體中(flag=true)
- main執行緒開始睡眠,此時CPU會切換到Thread-0執行緒執行,Thread-0執行緒同樣也會將全域性變數拷貝到自己的工作記憶體中(flag=true)
- Thread-0執行緒讀取自己的工作記憶體獲取flag的值為true,進入迴圈,一直迴圈
- 迴圈過程中CPU會切換到main執行緒執行,主執行緒修改了自己工作記憶體的值flag=false,因全域性變數使用了volatile, 此時JVM會使得其它引用該全域性變數的值失效,並立刻更改全域性變數的值,此時全域性變數的值為flag=false
- main執行緒修改完就結束了,此時又切換到Thread-0執行緒執行迴圈體,每迴圈一次就會讀取自己的工作記憶體中的flag值,當讀取時會發現自己工作記憶體中的flag值失效了,會重新從全域性變數中獲取新的值false快取到自己的工作區,此時因flag=false了,while迴圈就結束了,整個執行緒就結束了
三:volatile
一旦一個共享變數(類的成員變數、類的靜態成員變數)被volatile修飾之後,那麼就具備了兩層語義:
- 保證了不同執行緒對這個變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。
- 禁止進行指令重排序。
四:執行緒的三種特性
1.原子性
在Java中,對基本資料型別的變數的【讀取】和【賦值】操作是原子性操作,即這些操作是不可被中斷的,一次性必須執行完成的,要麼執行,要麼不執行。
// 原子操作
x = 10;
// 非原子操作: 1. 先讀取x的值; 2.將讀取的值賦值給y值; 總共分兩步操作
y = x;
// 非原子操作: 1. 先讀取x的值; 2.對x加1; 3. 將步驟2的結果賦值給x
x++;
// 和x++完全一樣,分三步
x = x + 1;
Java記憶體模型只保證了基本讀取和賦值是原子性操作,如果要實現更大範圍操作的原子性,可以通過synchronized和Lock來實現。由於synchronized和Lock能夠保證任一時刻只有一個執行緒執行該程式碼塊,那麼自然就不存在原子性問題了,從而保證了原子性。
2. 可見性
使用volatile關鍵字增加了例項變數在多個執行緒之間的可見性。但是volatile關鍵字最致命的缺點是不支援原子性。
synchronized和volatile比較:
關鍵字volatile是執行緒同步的輕量級實現,所以volatile效能肯定比synchronized要好,並且volatile只能修飾於變數,而synchronized可以修飾方法,以及程式碼塊。隨著JDK新版本的釋出,synchronized關鍵字在執行效率上得到很大提升,在開發中使用synchronized關鍵字的比率還是比較大的。
多執行緒訪問volatile不會發生阻塞,而synchronized會出現阻塞。
volatile能保證資料的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因為它將私有記憶體和公共記憶體中的資料做同步。
再次重申一下,關鍵字volatile解決的是變數在多個執行緒之間的可見性;而synchronized關鍵字解決的是多個執行緒之間訪問資源的同步性。
3. 有序性
相關文章(嘟嘟獨立部落格):http://tengj.top/2016/05/06/threadvolatile4/