4-4 執行緒安全性-可見性
阿新 • • 發佈:2018-12-14
一個執行緒對主記憶體的修改可以及時的被其他執行緒觀察到
導致共享變數線上程間不可見的原因
- 執行緒交叉執行
- 重排序結合線程交叉執行
- 共享變數更新後的值沒有在工作記憶體與主存間及時更新
可見性之synchronized
JMM關於synchronized的規定
- 執行緒解鎖前,必須把共享變數的最新值重新整理到主記憶體
- 執行緒加鎖時,將清空工作記憶體中共享變數的值,從而使用共享變數時需要從主記憶體中重新讀取最新的值(加鎖與解鎖是同一把鎖)
可見性之volatile
通過加入記憶體屏障和禁止重排序優化來實現
- 對volatile變數寫操作時,會在寫操作後加入一條store屏障指令,將本地記憶體中的共享變數值重新整理到主記憶體
- 對volatile變數讀操作時,會在讀操作前加入一條load屏障指令,從主記憶體中讀取共享變數
用volatile做計數操作,看是否是執行緒安全的
package com.mmall.concurrency.example.count; import com.mmall.concurrency.annoations.NotThreadSafe; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j @NotThreadSafe public class CountExample4 { // 請求總數 public static int clientTotal = 5000; // 同時併發執行的執行緒數 public static int threadTotal = 200; public static volatile int count = 0; public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); add(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("count:{}", count); /* 執行結果: 5000 4992 4998 因此volatile也是執行緒不安全的 原因是:假設開始時count=5,執行緒1和執行緒2同時做count++操作時,執行緒1和執行緒2都從記憶體中讀取到了count=5,然後執行緒1和執行緒2同時對count做加一操作,然後執行緒1把count的結果6由工作記憶體存回記憶體,執行緒2也把count的結果6由工作記憶體存回記憶體,因此執行結果小於等於5000 */ } private static void add() { count++; // 1、count // 2、+1 // 3、count } }
執行結果:
5000
4992
4998
因此volatile也是執行緒不安全的。 原因是:假設開始時count=5,執行緒1和執行緒2同時做count++操作時,執行緒1和執行緒2都從記憶體中讀取到了count=5,然後執行緒1和執行緒2同時對count做加一操作,然後執行緒1把count的結果6由工作記憶體存回記憶體,執行緒2也把count的結果6由工作記憶體存回記憶體,因此執行結果小於等於5000。
volatile使用
volatile適合使用在狀態標識的場景中,如下例項:(volatile還可以用於檢查2次的場景中)
volatile boolean inited = false;//全域性變數 //執行緒1: context = loadContext(); inited= true; // 執行緒2: while( !inited ){ sleep(); } doSomethingWithConfig(context)