volatile 非原子性
阿新 • • 發佈:2021-07-01
上一章節我們講了 volatile的可見性的,以及可見性的演示,這裡就會給大家產生一個誤區,這樣的使用方式很容易給人的感覺是
對volatile修飾的變數進行併發操作是執行緒安全的。 其實不然,用volatile修飾的變數只有兩個特性就是 可見性、禁止指令重排序。並不能保證執行緒的安全性
我們通過以下程式碼進行演示。
volatile int iCounter = 0; AtomicInteger atomicInteger = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { LatchTest latchTest = new LatchTest(); latchTest.startTaskAllInOnce(5); } private void m() throws InterruptedException { for (int i = 0; i < 1000000; i++) { iCounter++; atomicInteger.incrementAndGet(); } } public void startTaskAllInOnce(int threadNums) throws InterruptedException { final CountDownLatch startGate = new CountDownLatch(1); final CountDownLatch endGate = new CountDownLatch(threadNums); for (int i = 0; i < threadNums; i++) { new Thread(() -> { try { System.out.println("wait thread"); startGate.await(); try { m(); } finally { endGate.countDown(); } } catch (InterruptedException ie) { ie.printStackTrace(); } }).start(); } System.out.println("thread waited"); startGate.countDown(); endGate.await(); System.out.println("iCounter: " + iCounter + " atomicInteger :" + atomicInteger); } 輸出結果: volatile iCounter: 2972488 atomicInteger :5000000 volatile iCounter: 2737312 atomicInteger :5000000 volatile iCounter: 3613868 atomicInteger :5000000 volatile iCounter: 2081604 atomicInteger :5000000 volatile iCounter: 2875711 atomicInteger :5000000 volatile iCounter: 3037079 atomicInteger :5000000 volatile iCounter: 2806466 atomicInteger :5000000 volatile iCounter: 3218029 atomicInteger :5000000 volatile iCounter: 2608899 atomicInteger :5000000 volatile iCounter: 2513628 atomicInteger :5000000 AtomicInteger 是原子性操作,執行緒安全的,volatile 並不能保證執行緒安全。
這是因為雖然 volatile
保證了記憶體可見性,每個執行緒拿到的值都是最新值,但 count++
這個操作並不是原子的,這裡面涉及到獲取值、自增、賦值的操作並不能同時完成。
- 所以想到達到執行緒安全可以使這三個執行緒序列執行(其實就是單執行緒,沒有發揮多執行緒的優勢)。
- 也可以使用
synchronize
或者是鎖的方式來保證原子性。 - 還可以用
Atomic
包中AtomicInteger
來替換int
****,它利用了CAS
演算法來保證了原子性。