場景描述:多線程輸出1到100,對靜態Integer對象加鎖,synchronized代碼塊中操做Integer對象,發生線程安全問題(數據重複)java
代碼:數組
public class MyRunnableTest implements Runnable { public static Integer i = new Integer(0); @Override public void run() { while(true){ synchronized (i) { if(i<100){ i++; System.out.println(Thread.currentThread()+"i = " + i); }else { break; } } } } public static void main(String[] args) { Thread t1 = new Thread(new MyRunnableTest()); Thread t2 = new Thread(new MyRunnableTest()); t1.start(); t2.start(); } }
運行結果:緩存
Thread[Thread-0,5,main]i = 1 Thread[Thread-1,5,main]i = 3 Thread[Thread-1,5,main]i = 4 Thread[Thread-1,5,main]i = 5 Thread[Thread-1,5,main]i = 6 Thread[Thread-0,5,main]i = 5 Thread[Thread-1,5,main]i = 7 Thread[Thread-0,5,main]i = 8 Thread[Thread-1,5,main]i = 9 Thread[Thread-0,5,main]i = 10 Thread[Thread-1,5,main]i = 11 Thread[Thread-0,5,main]i = 12 Thread[Thread-1,5,main]i = 13
從運行結果中能夠發現發生了線程安全問題,爲何呢?爲何synchronized無效了。安全
個人排查思路:多線程
一、由於沒有進行任何的額外操做,因此首先定位問題在i++處併發
經過javap分析字節碼命令,總結:ide
能夠看出:i++的實際操做爲:Integer.valueOf(Integer.intValue(i)+1)測試
咱們在看看Integer中vauleOf的源碼:spa
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } cache[]數組初始化: cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++);
其中low爲-128,hight爲127。能夠了解到,當i的值爲-128~127時,是從IntegerCache中取的(能夠理解爲從緩存中取的),超過部分是new出來的對象。線程
二、目前咱們慢慢開始解開了問題的面紗,每當i發生自增後,所對象改變了,這裏咱們還須要清楚一下,synchronized在鎖對象發生改變時(測試發現,引用類型對象的內在屬性變化不會釋放鎖)會當即釋放鎖。因此這裏就會出現線程安全問題,並且在單核的運行環境下,全部的線程是併發執行而不是並行執行,當咱們運行到system.out.println時,鎖已經釋放了,假如這邊t1線程(當前運行的線程,i=1的狀況)釋放CPU資源,t2執行,這時i等於2(i++以前),當執行到system.out.println時釋放cpu資源(此時i=3),t1執行,i此時已經爲3了,因此輸出3,在釋放cpu資源,t2執行,輸出3,這時出現了輸出重複值的狀況。