JMM(java內存模型 Java Memory Model)自己是一種抽象的概念,描述一組規則後規範經過這組規範定義了程序中各個變量(包括實例字段,靜態變量和組成數組對象的元素)的訪問方式。java
JMM關於同步的規定:數組
因爲JMM運行程序的實體是線程,而每一個線程建立JVM都會爲其建立一個工做內存,工做內存是每一個線程的私有數據區域,而Java內存模型中規定全部變量都存儲在 主內存
,主內存是共享內存區域,全部線程均可以訪問,但線程對變量的操做(讀取或賦值)必須在工做內存中進行,首先要將內存從主內存拷貝到本身的工做內存空間,而後對變量進行操做,操做完成後再將變量寫回到主內存,不能直接操做主內存中的變量,各個線程中的工做內存中存儲着主內存中的變量副本拷貝,所以不一樣的線程間沒法訪問對方的工做內存,線程間的通訊(值傳遞)必須經過主內存來完成。訪問過程以下:安全
volatile是Java虛擬機提供的輕量級同步機制多線程
public class VolatileDemo {
public static void main(String[] args) throws InterruptedException {
MyData myData = new MyData();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.add60();
System.out.println(Thread.currentThread().getName()+ " 線程內修改num的值爲 :" + myData.number);
}, "Thread1").start();
while (myData.number == 0) {
}
TimeUnit.SECONDS.sleep(4);
System.out.println(Thread.currentThread().getName()+ " 主線程從循環中跳出");
}
}
class MyData {
volatile int number = 0;
void add60() {
this.number = 60;
}
}
複製代碼
以上代碼執行結果爲性能
Thread1 線程內修改num的值爲 :60
main 主線程從循環中跳出
複製代碼
由結果可知,線程之間是有可見性的,即線程Thread1 修改了number的值爲60,主線程的工做內存中的number讀到修改後的值爲60,便跳出循環,輸出循環外的語句。優化
若是將classData中的number
去掉volatile的修飾,則線程間沒有可見性,即主線程讀不到number修改後的值,main的工做內存中仍是保留number值爲0,則一直停留在循環中。this
不可見分割,完整性,某個線程正在作某個具體業務時,中間不可被加塞或者分割,須要總體完成,要麼成功,要麼失敗。spa
public class VolatileAtomicity {
public static void main(String[] args) {
MyDatas myDatas = new MyDatas();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myDatas.numPlusPlus();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(myDatas.number);
}
}
class MyDatas {
volatile int number = 0;
void numPlusPlus() {
number++;
}
}
複製代碼
以上代碼執行結果老是小於2000
首先咱們知道 number++
操做是分兩步執行:線程
以上,可知volatile是不符合JMM規定的原子性的,同時number++在多線程下是非線程安全的。code
volatile的有序性表如今 禁止指令重排
複製代碼
計算機在執行程序時,爲了提升性能,編譯器和處理器經常會對指令作重排
,通常分爲三類:
單線程環境裏面確保程序最終執行結果和代碼順序執行的結果一致
處理器在進行重排序時必須考慮指令間的數據依賴性
多線程環境中線程交替執行,因爲編譯器優化重排的存在,兩個線程中使用的變量可否保證一致性是沒法肯定的,結果沒法預測
void mysort() {
int x = 11;
int y = 12;
x = x + 5;
y = x * x;
}
複製代碼
以上代碼,執行順序 123四、213四、1324 是不影響最終的結果的,所以代碼編譯後指令順序不必定爲1234,這種行爲在多線程下可能會形成結果的偏差。
public class ReSoreSeqDemo {
int a = 0;
boolean flag = false;
public void method1() {
a = 1;
flag = true;
}
public void method2() {
if (flag) {
a = a + 5;
System.out.println("****value = " + a);
}
}
}
複製代碼
如上代碼,現有兩個線程A/B,分別執行method1和2,僅對於線程A來講,method1方法兩行執行順序前後沒有關係。可是若是method1順序變了,先執行flag=true
,這時線程B就進入method2的判斷中,a=5,再接着執行線程A的 a=1
,最後結果爲 a = 1,與指望結果 a=1
而後 a = a+5
的結果有差別。這就是須要禁止指令重排的緣由。
volatile實現禁止指令重排優化
,從而避免多線程環境下環境出現亂序執行的現象。
內存屏障又稱內存柵欄,是一個CPU指令,他的做用有兩個:
因爲編譯器和處理器都能執行指令重排優化。若是在指令間插入一條MemoryBarrier則會告訴編譯器和CPU, 無論什麼指令都不能和這條MemoryBarrier指令重排序,也就是經過內存屏障禁止在內存屏障先後的指令執行重排序優化
。內存屏障另外的一個做用是強制刷出各類CPU的煥醋拿數據,所以任何CPU上的線程都能讀取到這些數據的最新版本。
對volatile變量進行寫操做時,會在寫操做後加入一條store屏障指令,將工做內存中的共享變量之刷新到主內存中;
對volatile變量進行讀操做時,會在讀操做前加入一條load屏障指令,從主內存中讀取共享變量
工做內存和主內存同步延遲現象致使的可見性問題
對於指令重排致使的可見性問題和有序性問題
本文結束
內容根據尚硅谷視頻總結
歡迎訪問個人我的博客 justd的博客