在有關java線程的討論中,一個常不正確的知識是:“原子操作不需要同步控制”。原子操作是不能被線程調度戒指中斷的操作;一旦操作開始,那么它一定可以在可能發生的“上下文切換”之前執行完畢。依賴於原子性是很棘手而且是很危險的。
原子性可以應用於除了long和double之外的所有基本類型之上的“簡單操作”。對於讀取和寫入除long和double之外的基本類型變量這樣的操作,可以保證它們會被當作不可分(原子)的操作來操作內存,但是JVM將64位的變量的讀取和寫入分為兩個分離的32位操作來執行,這就產生了在一個讀取和寫入操作中間發生上下文切換,從而導致不同的任務可以看不到正確結果的可能性(有時候,這也成為字撕裂,因為你可能會看到部分被修改或的值)。當你定義long和double時,如果使用volatile關鍵字,就會獲得(簡單的賦值和返回操作)原子性(注意:在java5以前,volatile關鍵字一直未能正常工作)因此,原子操作可由線程機制來保證其不可中斷。
在多核處理器上,相對於單核處理器而已, 可視性問題比原子性問題多得多。一個任務做出的修改,即使在不中斷的意義上來將時原子性的,但是對其他任務時不可視的(例如:修改只是暫時的存在本地副處理器的緩存中),因此不同的任務對應用的狀態有不同的視圖。另一個方面,同步機制強制在處理器系統中,一個任務做出的修改必須在應用中是可視的。如果沒有同步機制,那么修改時可視性將無法確定,
volatile關鍵字還確保了應用中的可視性,如果一個變量被volatile修飾,那么只要對這個域產生了寫操作,那么所有的讀操作都將可以看到這個修改。即便使用了本地緩存,情況也是如此。volatile域會立即被寫入到主存中去,而讀操作就發生在主存中。
理解原子性和易變性使不用的概念這一點很重要。在非olatile域上的原子操作不必刷新到主存中去,因此其他讀取該域的任務也不必看到這個新值。如果多個任務在同時訪問某個域,那么這個域就應該是volatile的,否則,這個域就應該只能經由同步來訪問,同步也會導致向主存中刷新,因此如果一個域完全有synchronized方法或語句塊來防護,那就不必將該變量設置位volatile的。
一個任務所作的任何寫入操作對這個任務來說都是可視的,因此如果它只需要在這個任務內部可視,那就不必將該變量設置位volatile的。
當一個域的值依賴於它之前的值時(例如遞增操作),volatile就無法工作了。如果某個域的值收到其他域的值的限制,那么volatile也無法工作,例如Range類的lower和uppeer邊界就必須遵循 lower<=upper的限制。
使用volatile而不是synchronized的唯一安全的情況就是類中只有一個可變的域。
如果盲目的應用原子性的概念,那么下面這個get()操作就會產生問題:
package tij; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by huaox on 2017/4/19. */ class TestA implements Runnable{ private int i=0; int get(){return i;} //此時不是同步方法 synchronized void add(){i++;i++;} @Override public void run() { while (true){ add(); System.out.println(i); } } } public class AtomicityTest { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); TestA a = new TestA(); service.execute(a); while (true) { int val = a.get(); if(val%2 != 0){ System.out.println(val); System.exit(0); } } } }
輸出結果:

280
282
284
286
288
290
292
294
296
298
300
302
304
306
308
310
312
314
316
318
320
322
324
326
328
330
332
334
336
338
340
342
344
346
348
350
352
354
356
358
360
362
364
366
368
370
372
374
376
378
380
382
384
386
388
390
392
394
396
398
400
402
404
406
408
410
412
414
416
418
420
422
424
426
428
430
432
434
436
438
440
442
444
446
448
450
452
454
456
458
460
462
464
466
468
470
472
474
476
478
480
482
484
486
488
490
492
494
496
498
500
502
504
506
508
1
510
512
514
516
518
520
522
Process finished with exit code 0
可見真的產生了奇數,盡管return i屬於原子操作,但缺少同步語句使其數值處於不穩定的中間狀態時被讀取,由於還不是volatile修飾的,還存在可視性問題。那么改為volatile修飾的效果又如何呢?
package tij; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by huaox on 2017/4/19. */ class TestA implements Runnable{ private volatile int i=0; int get(){return i;} //此時不是同步方法 synchronized void add(){i++;i++;} @Override public void run() { while (true){ add(); System.out.println(i); } } } public class AtomicityTest { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); TestA a = new TestA(); service.execute(a); while (true) { int val = a.get(); if(val%2 != 0){ System.out.println(val); System.exit(0); } } } }
輸出結果:
1
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
Process finished with exit code 0
最終還是產生奇數了。所以我們應該用同步語句來修飾。
package tij; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by huaox on 2017/4/19. */ class TestA implements Runnable{ private int i=0; synchronized int get(){return i;} //此時不是同步方法 synchronized void add(){i++;i++;} @Override public void run() { while (true){ add(); System.out.println(i); } } } public class AtomicityTest { public static void main(String[] args) { new Timer().schedule(new TimerTask() { @Override public void run() { System.out.println("Aborting"); System.exit(0); } }, 5000);//在5秒后結束 ExecutorService service = Executors.newCachedThreadPool(); TestA a = new TestA(); service.execute(a); while (true) { int val = a.get(); if(val%2 != 0){ System.out.println(val); System.exit(0); } } } }
現在不會存在臟讀問題了。那么我們也可以使用原子類來操作。
api包描述:
軟件包 java.util.concurrent.atomic 的描述 類的小工具包,支持在單個變量上解除鎖的線程安全編程。事實上,此包中的類可將 volatile 值、字段和數組元素的概念擴展到那些也提供原子條件更新操作的類,其形式如下: boolean compareAndSet(expectedValue, updateValue); 如果此方法(在不同的類間參數類型也不同)當前保持 expectedValue,則以原子方式將變量設置為 updateValue,並在成功時報告 true。此包中的類還包含獲取並無條件設置值的方法,以及以下描述的較弱條件的原子更新操作 weakCompareAndSet。 這些方法的規范使實現能夠使用當代處理器上提供的高效機器級別原子指令。但是在某些平台上,該支持可能需要某種形式的內部鎖。因而,該方法不能嚴格保證不被阻塞 - 執行操作之前可能暫時阻塞線程。 類 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的實例各自提供對相應類型單個變量的訪問和更新。每個類也為該類型提供適當的實用工具方法。
例如,類 AtomicLong 和 AtomicInteger 提供了原子增量方法。一個應用程序將按以下方式生成序列號: class Sequencer { private final AtomicLong sequenceNumber = new AtomicLong(0); public long next() { return sequenceNumber.getAndIncrement(); } } 原子訪問和更新的內存效果一般遵循以下可變規則,正如 The Java Language Specification, Third Edition (17.4 Memory Model) 中的聲明: get 具有讀取 volatile 變量的內存效果。 set 具有寫入(分配)volatile 變量的內存效果。 除了允許使用后續(但不是以前的)內存操作,其自身不施加帶有普通的非 volatile 寫入的重新排序約束,lazySet 具有寫入(分配)volatile 變量的內存效果。
在其他使用上下文中,當為 null 時(為了垃圾回收),lazySet 可以應用不會再次訪問的引用。
weakCompareAndSet 以原子方式讀取和有條件地寫入變量但不 創建任何 happen-before 排序,因此不提供與除 weakCompareAndSet 目標外任何變量以前或后續讀取或寫入操作有關的任何保證。 compareAndSet 和所有其他的讀取和更新操作(如 getAndIncrement)都有讀取和寫入 volatile 變量的內存效果。
除了包含表示單個值的類之外,此包還包含 Updater 類,該類可用於獲取任意選定類的任意選定 volatile 字段上的 compareAndSet 操作。
AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater 和 AtomicLongFieldUpdater 是基於反射的實用工具,可以提供對關聯字段類型的訪問。
它們主要用於原子數據結構中,該結構中同一節點(例如,樹節點的鏈接)的幾個 volatile 字段都獨立受原子更新控制。這些類在如何以及何時使用原子更新方面具有更大的靈活性,
但相應的弊端是基於映射的設置較為拙笨、使用不太方便,而且在保證方面也較差。 AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 類進一步擴展了原子操作,對這些類型的數組提供了支持。
這些類在為其數組元素提供 volatile 訪問語義方面也引人注目,這對於普通數組來說是不受支持的。 原子類也支持 weakCompareAndSet 方法,該方法具有受限制的適用性。在某些平台上,弱版本在正常情況下可能比 compareAndSet 更有效,
但不同的是 weakCompareAndSet 方法的任何給定調用可能意外 返回 false(即沒有明確的原因)。返回 false 僅意味着可以在需要時重新嘗試操作,
具體取決於重復執行調用的保證,當該變量保持 expectedValue 並且沒有其他線程也在嘗試設置該變量時,最終將獲得成功。
(例如,這樣的虛假失敗可能是由於內存爭用的結果,該爭用與期望值和當前值是否相等無關)。 此外,weakCompareAndSet 不提供通常需要同步控制的排序保證。
但是,在這樣的更新與程序的其他 happen-before 排序不相關時,該方法可用於更新計數器和統計數據。當一個線程看到對 weakCompareAndSet 導致的原子變量的更新時,
它不一定能看到在 weakCompareAndSet 之前發生的對任何其他 變量的更新。例如,在更新性能統計數據時,這也許可以接受,但其他情況幾乎不可以。 AtomicMarkableReference 類將單個布爾值與引用關聯起來。例如,可以在數據結構內部使用此位,這意味着引用的對象在邏輯上已被刪除。
AtomicStampedReference 類將整數值與引用關聯起來。例如,這可用於表示與更新系列對應的版本號。 設計原子類主要用作各種構造塊,用於實現非阻塞數據結構和相關的基礎結構類。compareAndSet 方法不是鎖的常規替換方法。僅當對象的重要更新限定於單個 變量時才應用它。 原子類不是 java.lang.Integer 和相關類的通用替換方法。它們不 定義諸如 hashCode 和 compareTo 之類的方法。(因為原子變量是可變的,所以對於哈希表鍵來說,它們不是好的選擇。)另外,僅為那些通常在預期應用程序中使用的類型提供類。例如,沒有表示 byte 的原子類。這種情況不常見,如果要這樣做,可以使用 AtomicInteger 來保持 byte 值,並進行適當的強制轉換。也可以使用 Float.floatToIntBits 和 Float.intBitstoFloat 轉換來保持 float 值,使用 Double.doubleToLongBits 和 Double.longBitsToDouble 轉換來保持 double 值。
用原子類改寫上面的代碼:
package tij; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; /** * Created by huaox on 2017/4/19. */ class TestA implements Runnable{ private AtomicInteger i= new AtomicInteger(0); int get(){return i.get();} void add(){i.addAndGet(2);} @Override public void run() { while (true){ add(); System.out.println(i.get()); } } } public class AtomicityTest { public static void main(String[] args) { new Timer().schedule(new TimerTask() { @Override public void run() { System.out.println("Aborting"); System.exit(0); } }, 5000);//在5秒后結束 ExecutorService service = Executors.newCachedThreadPool(); TestA a = new TestA(); service.execute(a); while (true) { int val = a.get(); if(val%2 != 0){ System.out.println(val); System.exit(0); } } } }
這個程序並不會失敗
原子類源碼:
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
1、最外層是一個死循環
2、先獲取舊值,將其復制到一個局部變量上
3、將局部變量值+1
4、比較舊值是否變化,如果沒變化,說明沒有其它線程對舊值修改,直接將新值覆蓋到舊值,並返回新值,退出循環
5、如果舊值被修改了,開始下一輪循環,重復剛才這一系列操作,直到退出循環。
所以,第4步的compareAndSet其實是關鍵,繼續看源碼:
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
最終看到的是一個native方法(說明依賴不同OS的原生實現)
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
直接調用的是UnSafe這個類的compareAndSwapInt方法
全稱是sun.misc.Unsafe. 這個類是Oracle(Sun)提供的實現. 可以在別的公司的JDK里就不是這個類了
compareAndSwapInt的實現
/** * Atomically update Java variable to <tt>x</tt> if it is currently * holding <tt>expected</tt>. * @return <tt>true</tt> if successful */ public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
可以看到, 不是用Java實現的, 而是通過JNI調用操作系統的原生程序.
compareAndSwapInt的native實現
如果你下載了OpenJDK的源代碼的話在hotspot\src\share\vm\prims\目錄下可以找到unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) UnsafeWrapper("Unsafe_CompareAndSwapInt"); oop p = JNIHandles::resolve(obj); jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); return (jint)(Atomic::cmpxchg(x, addr, e)) == e; UNSAFE_END
可以看到實際上調用Atomic類的cmpxchg方法.
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { // alternative for InterlockedCompareExchange int mp = os::is_MP(); __asm { mov edx, dest mov ecx, exchange_value mov eax, compare_value LOCK_IF_MP(mp) cmpxchg dword ptr [edx], ecx } }
在這里可以看到是用嵌入的匯編實現的, 關鍵CPU指令是 cmpxchg
到這里沒法再往下找代碼了. 也就是說CAS的原子性實際上是CPU實現的. 其實在這一點上還是有排他鎖的. 只是比起用synchronized, 這里的排他時間要短的多. 所以在多線程情況下性能會比較好.