從volatile說到i++的線程安全問題
閱讀本文大概需要 4 分鐘。
簡介
Thread的本地內(nèi)存
每個Thread都擁有自己的線程存儲空間
Thread何時同步本地存儲空間的數(shù)據(jù)到主存是不確定的
例子

借用Google JEREMY MANSON 的解釋,上圖表示兩個線程并發(fā)執(zhí)行,而且代碼順序上為Thread1->Thread2。
1. 不用 volatile
假如ready字段不使用volatile,那么Thread 1對ready做出的修改對于Thread2來說未必是可見的,是否可見是不確定的.假如此時thread1 ready泄露了(leak through)了,那么Thread 2可以看見ready為true,但是有可能answer的改變并沒有泄露,則thread2有可能會輸出 0 (answer=42對thread2并不可見)。
每次修改volatile變量都會同步到主存中
每次讀取volatile變量的值都強制從主存讀取最新的值(強制JVM不可優(yōu)化volatile變量,如JVM優(yōu)化后變量讀取會使用cpu緩存而不從主存中讀取)
線程 A 中寫入 volatile 變量之前可見的變量, 在線程 B 中讀取該 volatile 變量以后, 線程 B 對其他在 A 中的可見變量也可見. 換句話說, 寫 volatile 類似于退出同步塊, 而讀取 volatile 類似于進入同步塊
注意
public class VolatileTest {private static volatile int count = 0;private static final int times = Integer.MAX_VALUE;public static void main(String[] args) {long curTime = System.nanoTime();Thread decThread = new DecThread();decThread.start();// 使用run()來運行結(jié)果為0,原因是單線程執(zhí)行不會有線程安全問題// new DecThread().run();System.out.println("Start thread: " + Thread.currentThread() + " i++");for (int i = 0; i < times; i++) {count++;}System.out.println("End thread: " + Thread.currentThread() + " i--");// 等待decThread結(jié)束while (decThread.isAlive());long duration = System.nanoTime() - curTime;System.out.println("Result: " + count);System.out.format("Duration: %.2fs\n", duration / 1.0e9);}private static class DecThread extends Thread {@Overridepublic void run() {System.out.println("Start thread: " + Thread.currentThread() + " i--");for (int i = 0; i < times; i++) {count--;}System.out.println("End thread: " + Thread.currentThread() + " i--");}}}
Start thread: Thread[main,5,main] i++
Start thread: Thread[Thread-0,5,main] i--
End thread: Thread[main,5,main] i--
End thread: Thread[Thread-0,5,main] i--
Result: -460370604
Duration: 67.37s
void f1() { i++; }
void f1();Code:0: aload_01: dup2: getfield #2; //Field i:I5: iconst_16: iadd7: putfield #2; //Field i:I10: return
Thread1 Thread2r1 = i; r3 = i;r2 = r1 + 1; r4 = r3 + 1;i = r2; i = r4;
線程同步問題的解決
package com.qunar.atomicinteger;import java.util.concurrent.atomic.AtomicInteger;/*** @author zhenwei.liu created on 2013 13-9-2 下午10:18* @version $Id$*/public class SafeTest {private static AtomicInteger count = new AtomicInteger(0);private static final int times = Integer.MAX_VALUE;public static void main(String[] args) {long curTime = System.nanoTime();Thread decThread = new DecThread();decThread.start();// 使用run()來運行結(jié)果為0,原因是單線程執(zhí)行不會有線程安全問題// new DecThread().run();System.out.println("Start thread: " + Thread.currentThread() + " i++");for (int i = 0; i < times; i++) {count.incrementAndGet();}// 等待decThread結(jié)束while (decThread.isAlive());long duration = System.nanoTime() - curTime;System.out.println("Result: " + count);System.out.format("Duration: %.2f\n", duration / 1.0e9);}private static class DecThread extends Thread {public void run() {System.out.println("Start thread: " + Thread.currentThread() + " i--");for (int i = 0; i < times; i++) {count.decrementAndGet();}System.out.println("End thread: " + Thread.currentThread() + " i--");}}}
Start thread: Thread[main,5,main] i++
Start thread: Thread[Thread-0,5,main] i--
End thread: Thread[Thread-0,5,main] i--
Result: 0
Duration: 105.15
結(jié)論
volatile解決了線程間共享變量的可見性問題
使用volatile會增加性能開銷
volatile并不能解決線程同步問題
解決i++或者++i這樣的線程同步問題需要使用synchronized或者AtomicXX系列的包裝類,同時也會增加性能開銷
推薦閱讀:
微信掃描二維碼,關(guān)注我的公眾號
朕已閱?
評論
圖片
表情

