原文鏈接:http://www.cnblogs.com/zhengbin/p/5653051.html
一、補充概念
1.什么是線程安全性?
《Java Concurrency in Practice》中有提到:當多個線程訪問某個類時,這個類始終都能表現出正確的行為,那么就稱這個類是線程安全的。
2.Java中的“同步”
Java中的主要同步機制是關鍵字“synchronized”,它提供了一種獨占的加鎖方式,但“同步”這個術語還包括volatile類型的變量,顯式鎖(Explicit Lock)以及原子變量。
2.原子性
原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型)這個操作是不可分割的,那么我們說這個操作時原子操作。再比如:a++;這個操作實際是a = a + 1;是可分割的,所以他不是一個原子操作。非原子操作都會存在線程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那么我們稱它具有原子性。java的concurrent包下提供了一些原子類,我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。
二、實例源碼
1 public class IncrementTestDemo { 2 3 public static int count = 0; 4 public static Counter counter = new Counter(); 5 public static AtomicInteger atomicInteger = new AtomicInteger(0); 6 volatile public static int countVolatile = 0; 7 8 public static void main(String[] args) { 9 for (int i = 0; i < 10; i++) { 10 new Thread() { 11 public void run() { 12 for (int j = 0; j < 1000; j++) { 13 count++; 14 counter.increment(); 15 atomicInteger.getAndIncrement(); 16 countVolatile++; 17 } 18 } 19 }.start(); 20 } 21 try { 22 Thread.sleep(3000); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 27 System.out.println("static count: " + count); 28 System.out.println("Counter: " + counter.getValue()); 29 System.out.println("AtomicInteger: " + atomicInteger.intValue()); 30 System.out.println("countVolatile: " + countVolatile); 31 } 32 33 } 34 35 class Counter { 36 private int value; 37 38 public synchronized int getValue() { 39 return value; 40 } 41 42 public synchronized int increment() { 43 return ++value; 44 } 45 46 public synchronized int decrement() { 47 return --value; 48 } 49 }
輸出結果:
static count: 9952 Counter: 10000 AtomicInteger: 10000 countVolatile: 9979
第一行與最后一行,每次運行將得到不同的結果,但是中間兩行的結果相同。
通過上面的例子說明,要解決自增操作在多線程環境下線程不安全的問題,可以選擇使用Java提供的原子類,或者使用synchronized同步方法。
而通過Volatile關鍵字,並不能解決非原子操作的線程安全性。Volatile詳解
三、Java中的自增原理
雖然遞增操作++i是一種緊湊的語法,使其看上去只是一個操作,但這個操作並非原子的,因而它並不會作為一個不可分割的操作來執行。實際上,它包含了三個獨立的操作:讀取count的值,將值加1,然后將計算結果寫入count。這是一個“讀取 - 修改 - 寫入”的操作序列,並且其結果狀態依賴於之前的狀態。
下面寫一個簡單的類,用jdk中的工具javap來反編譯Java字節碼文件。
/** * @author zhengbinMac */ public class TestDemo { public static int count; public void code() { count++; } }
localhost:Increment zhengbinMac$ javap -c TestDemo 警告: 二進制文件TestDemo包含Increment.TestDemo Compiled from "TestDemo.java" public class Increment.TestDemo { public static int count; public Increment.TestDemo(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void code(); Code: 0: getstatic #2 // Field count:I 3: iconst_1 4: iadd 5: putstatic #2 // Field count:I 8: return }
如上字節碼,我們發現自增操作包括取數(getstatic #2)、加一(iconst_1和iadd)、保存(putstatic #2),並不是我們認為的一條機器指令搞定的。