最近寫了一個程序,是采用多線程往redis里面寫入數據,想統計一下一共寫了多少條數據,於是用了一個static的全局變量count來累加,這塊代碼抽象出來就是這樣的:
1 public class MultiThread implements Runnable { 2 private String name; 3 private static Integer count = 0; 4 5 public MultiThread() { 6 } 7 8 public MultiThread(String name) { 9 this.name = name; 10 } 11 12 public void run() { 13 for (int i = 0; i < 5; i++) { 14 //模擬寫入redis的IO操作消耗時間 15 try { 16 Thread.sleep(200); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 21 //累加寫入次數 22 count++; 23 System.out.println(name + "運行 " + i + " 寫入條數:" + count); 24 } 25 } 26 27 public static void main(String[] args) { 28 for (int i = 0; i < 100; i++) { 29 new Thread(new MultiThread("Thread"+i)).start(); 30 } 31 } 32 }
啟動了100個線程,每個線程寫入5次,預計結果應該是500,但是實際結果是這樣的:
分析了原因,應該是因為count++不是原子操作,這句代碼實際上是執行了3步操作:1,獲取類變量count值。2,count+1。3,將count+1后的結果賦值給類變量count。在這3步中間都有可能中斷執行其他線程。這樣比如線程1先獲取了count=0,這時候切換到線程2,線程2獲取了count=0,然后又切換到線程1,線程1執行count++=1並修改了類變量count=1,之后又切換到線程2,線程2對之前它獲取到的count=0執行count++=1並修改類變量count=1。問題出現了,明明有兩個線程對count累加了兩次,但是由於count沒有加鎖,最終類變量只加了1。
根據分析修改程序成下面這樣,給count加了同步,將上面代碼中第22行的"count++"改為了:
1 synchronized (count) { 2 count++; 3}
這次運行前兩次都正常顯示了500,但是多運行幾次發現個別時候仍然有問題:
再次分析原因,分析不出來了,開始各種修改各種試,終於成功試驗出了正確代碼,將count++移到外面,封裝到類的靜態同步方法里:
1 public class MultiThread implements Runnable { 2 private String name; 3 private static Integer count = 0; 4 5 public MultiThread() { 6 } 7 8 public MultiThread(String name) { 9 this.name = name; 10 } 11 12 public void run() { 13 for (int i = 0; i < 5; i++) { 14 //模擬寫入redis的IO操作消耗時間 15 try { 16 Thread.sleep(200); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 21 //累加寫入次數 22 countPlus(); 23 System.out.println(name + "運行 " + i + " 寫入條數:" + count); 24 } 25 } 26 27 private static synchronized void countPlus(){ 28 count++; 29 } 30 31 32 public static void main(String[] args) { 33 for (int i = 0; i < 100; i++) { 34 new Thread(new MultiThread("Thread"+i)).start(); 35 } 36 } 37 }
這次運行多次結果均是正常的,為了確保結果正確,又把線程數改為1000試驗了多次,結果也是正確的(5000,不過要好好找找了,因為countPlus()和sysout在多個線程里會交錯執行,這個5000不一定會出現在什么位置...從最后一行往前找吧...)。
看着這個運行結果,基本能猜到原因了,原因就出在這一句:
1 new Thread(new MultiThread("Thread"+i)).start();
這里為每個線程new了一個對象,所以之前的
1 synchronized (count) { 2 count++; 3 }
的作用范圍是同一個對象的多個線程,也就是說它能夠確保Thread1對象的多個線程訪問count的時候是同步的,而實際上我們是多線程多實例,每個線程都對應一個不同的對象,所以這句代碼實際上是不能起到同步count的作用的。