Java內存模型(JMM)描述了Java程序中變量(線程公用變量)的訪問規則(能夠看作是一種規範),以及在JVM中將變量存儲到內存和內存中讀取出變量這樣的底層細節。java
而且規定:git
舉個例子(修改線程A中的變量):安全
若是知足上面兩點,也就是說線程A中更新的共享變量線程B中可以及時獲得更新,就稱爲變量是可見的,反正則是不可見。多線程
as-if-serial
代碼書寫順序與代碼實際執行的順序不一樣,指令重排序是編譯器或處理器爲了提升程序性能而作出的優化。併發
主要有三種方式:ide
舉個例子:性能
int a = 2; int b = 3; int c = a + b;
在實際運行中多是:優化
int b = 3; int a = 2; int c = a + b;
上述例子中演示了指令重排序,那麼可能會有人問,第三行代碼若是重排序到前兩行代碼以前,豈不是會報錯嗎?this
有一個as-if-serial
,其內容以下:線程
不管如何重排序,程序執行的結果應該與代碼順序執行結果一致。(Java編譯器、運行時和處理器都要保證Java在單線程下遵循as-if-serial語義)
所以在單線程的狀況下你沒必要擔憂指令重排序帶來什麼不良後果。
可是在多線程交錯執行時,重排序就可能形成內存可見性問題,詳情請繼續閱讀下文。
package cn.com.dotleo; /** * Created by liufei on 2018/6/16. */ public class SynchronizedDemo { // 共享變量 private boolean ready = false; private int result = 0; private int number = 1; // 寫操做 public void write() { ready = true; // 1.1 number = 2; // 1.2 } // 讀操做 public void read() { if (ready) { // 2.1 result = number * 3; //2.2 } System.out.println("result的值爲:" + result); } private class ReadWriteThread extends Thread { private boolean flag; public ReadWriteThread(boolean flag) { this.flag = flag; } // 根據傳入執行不一樣的讀寫操做 @Override public void run() { if (flag) { write(); } else { read(); } } } public static void main(String[] args) { SynchronizedDemo demo = new SynchronizedDemo(); // 啓動寫線程 demo.new ReadWriteThread(true).start(); // 啓動讀線程 demo.new ReadWriteThread(false).start(); } }
代碼參見SynchronizedDemo
對於這個程序,若是執行main方法將可能有一下幾種狀況:
其實,2.1和2.2也是能夠重排序的:
int temp = number * 3; if (ready) { result = temp; }
所以就有了:
到這裏,能夠總結一下爲何會出現共享變量線程不安全的主要緣由了:
從Java語言層面講,主要支持一下兩種方式:
Synchronized
Volatile
不包括JDK 1.5提供的Java併發包
JMM關於Synchronized
的兩條規定:
咱們先來修改一下原來的代碼,保證其共享變量在多線程下的可見性。
// 寫操做 public synchronized void write() { ready = true; // 1.1 number = 2; // 1.2 } // 讀操做 public synchronized void read() { if (ready) { // 2.1 result = number * 3; //2.2 } System.out.println("result的值爲:" + result); }
爲何這個操做能保證其可見性呢?咱們經過分析致使共享變量線程不可見的3個緣由逐一分析:
Synchronized
關鍵詞加鎖後,保證了線程不會交叉執行as-if-serial
語義Synchronized
的兩條規定能保證它及時獲取而且在操做結束後及時更新主內存中的共享變量的值。關於volatile
,它有如下特性:
它的這些特性是經過內存屏障和禁止指令重排序優化來實現的。
store
屏障指令,讓主內存中的變量及時更新load
屏障指令,更新主內存中的變量舉一個volatile
的例子說明它不具有原子性。
package cn.com.dotleo; /** * Created by liufei on 2018/6/16. */ public class VolatileDemo { private volatile int num = 0; public int getNum() { return this.num; } public void increase() { this.num++; } public static void main(String[] args) { final VolatileDemo volatileDemo = new VolatileDemo(); for (int i = 0; i < 500; i++) { new Thread(new Runnable() { public void run() { volatileDemo.increase(); } }).start(); } // 爲了讓全部線程執行完畢 // 若是還有子線程執行 // 主線程讓出cpu資源 while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println("num" + volatileDemo.getNum()); } }
代碼參見VolatileDemo
該程序的運行結果不老是500。由於increase()
方法中的num++
並不是原子操做,它包括了:
試分析一種狀況:
至此,兩次循環卻少加了,致使並不是咱們想要的num最終 = 500
要在多線程中安全的使用volatile
,必須同時知足: