引言
Java從JDK1.5開始提供了java.util.concurrent.atomic包,方便程序員在多線程環境下,無鎖的進行原子操做。底層使用了Unsafe類的native方法,提供了硬件級別的原子操做,可是不一樣的CPU架構可能提供的原子指令不同,也有可能須要某種形式的內部鎖,因此該包下的方法不能絕對保證線程不被阻塞。java
Atomic包介紹
在Atomic包裏一共有17個類,四種原子更新方式,分別是原子更新基本類型,原子更新數組,原子更新引用和原子更新字段。Atomic包裏的類基本都是使用Unsafe的CAS實現的包裝類。程序員
原子更新基本類型類
用於經過原子的方式更新基本類型,Atomic包提供瞭如下三個類:數組
AtomicBoolean:原子更新布爾類型。
AtomicInteger:原子更新整型。
AtomicLong:原子更新長整型。
AtomicInteger的經常使用方法以下:安全
int addAndGet(int delta) :以原子方式將輸入的數值與實例中的值(AtomicInteger裏的value)相加,並返回結果
boolean compareAndSet(int expect, int update) :若是輸入的數值等於預期值,則以原子方式將該值設置爲輸入的值。
int getAndIncrement():以原子方式將當前值加1,注意:這裏返回的是自增前的值。
void lazySet(int newValue):最終會設置成newValue,使用lazySet設置值後,可能致使其餘線程在以後的一小段時間內仍是能夠讀到舊的值。關於該方法的更多信息能夠參考併發網翻譯的一篇文章《AtomicLong.lazySet是如何工做的?》
int getAndSet(int newValue):以原子方式設置爲newValue的值,並返回舊值。
AtomicInteger例子代碼以下:多線程
import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest { static AtomicInteger ai = new AtomicInteger(1); public static void main(String[] args) { System.out.println(ai.getAndIncrement()); System.out.println(ai.get()); } }
輸出架構
1 2
餐後甜點
Atomic包提供了三種基本類型的原子更新,可是Java的基本類型裏還有char,float和double等。那麼問題來了,如何原子的更新其餘的基本類型呢?Atomic包裏的類基本都是使用Unsafe實現的,讓咱們一塊兒看下Unsafe的源碼,發現Unsafe只提供了三種CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源碼,發現其是先把Boolean轉換成整型,再使用compareAndSwapInt進行CAS,因此原子更新double也能夠用相似的思路來實現。併發
原子更新數組類
經過原子的方式更新數組裏的某個元素,Atomic包提供瞭如下三個類:函數
AtomicIntegerArray:原子更新整型數組裏的元素。
AtomicLongArray:原子更新長整型數組裏的元素。
AtomicReferenceArray:原子更新引用類型數組裏的元素。
AtomicIntegerArray類主要是提供原子的方式更新數組裏的整型,其經常使用方法以下高併發
int addAndGet(int i, int delta):以原子方式將輸入值與數組中索引i的元素相加。
boolean compareAndSet(int i, int expect, int update):若是當前值等於預期值,則以原子方式將數組位置i的元素設置成update值。
實例代碼以下:性能
public class AtomicIntegerArrayTest { static int[] value = new int[] { 1, 2 }; static AtomicIntegerArray ai = new AtomicIntegerArray(value); public static void main(String[] args) { ai.getAndSet(0, 3); System.out.println(ai.get(0)); System.out.println(value[0]); } }
輸出
3 1
AtomicIntegerArray類須要注意的是,數組value經過構造方法傳遞進去,而後AtomicIntegerArray會將當前數組複製一份,因此當AtomicIntegerArray對內部的數組元素進行修改時,不會影響到傳入的數組。
原子更新引用類型
原子更新基本類型的AtomicInteger,只能更新一個變量,若是要原子的更新多個變量,就須要使用這個原子更新引用類型提供的類。Atomic包提供瞭如下三個類:
AtomicReference:原子更新引用類型。
AtomicReference的使用例子代碼以下:
import java.util.concurrent.atomic.AtomicReference; import static java.lang.System.out; public class AtomicReferenceTest { public static void main(String[] args) { AtomicReference<User> atomicUserRef = new AtomicReference(); User user = new User("last", 15); atomicUserRef.set(user); User updateUser = new User("soul", 17); atomicUserRef.compareAndSet(user, updateUser); out.printf("name:%s,age:%d \n",atomicUserRef.get().getName(),atomicUserRef.get().getAge()); } static class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } } }
輸出
name:soul,age:17
原子更新字段類
若是咱們只須要某個類裏的某個字段,那麼就須要使用原子更新字段類AtomicReferenceFieldUpdater。
要想使用原子更新字段類須要注意如下問題:
AtomicReferenceFieldUpdater不能更新原子類型屬性,原子類型屬性要用AtomicIntegerFieldUpdater,它要注意的問題同上,除了第二條對屬性的限制。它要求屬性必須是int,而不能是Integer.
和AtomicIntegerFieldUpdater相似的是AtomicLongFieldUpdater,它們是原子更新基本類型字段的更新器。
示例代碼以下:
public class FieldUpdaterTest { public static void main(String[] args) { //定義一個更新器,指定更新的對象,字段類型以及字段名字 AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(User.class, Integer.class, "age"); //要被更新的對象 User user1 = new User("user1", 1); //更新對象的屬性 updater.compareAndSet(user1, user1.age, 3); System.out.println(user1.age); //另外一個要被更新的對象 User user2 = new User("user2", 1); //更新對象的屬性 updater.compareAndSet(user2, user2.age, 4); System.out.println(user2.age); //Integer類型屬性更新器,它的屬性必須是原子類型,而不能是包裝類型 AtomicIntegerFieldUpdater integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "old"); //要被更新的對象 User user3 = new User("user3", 1, 4); //更新對象的屬性 integerFieldUpdater.compareAndSet(user3, user3.old, 5); System.out.println(user3.old); } static class User { public volatile String userName; public volatile Integer age; public volatile int old; public User(String userName, int age) { this.userName = userName; this.age = age; } public User(String userName, int age, int old) { this.userName = userName; this.age = age; this.old = old; } } }
輸出
10 11
AtomicReference經過volatile和Unsafe提供的CAS函數實現原子操做。 自旋+CAS的無鎖操做保證共享變量的線程安全。
可是CAS操做可能存在ABA問題。AtomicStampedReference和AtomicMarkableReference的出現就是爲了解決這問題。
AtomicStampedReference
構造方法中initialStamp(時間戳)用來惟一標識引用變量,在構造器內部,實例化了一個Pair對象,Pair對象記錄了對象引用和時間戳信息,採用int做爲時間戳,實際使用的時候,要保證時間戳惟一(通常作成自增的),若是時間戳若是重複,還會出現ABA的問題。
AtomicStampedReference中的每個引用變量都帶上了pair.stamp這個時間戳,這樣就能夠解決CAS中的ABA的問題。
示例代碼:
public class StampedReferenceTest { public static void main(String[] args) throws InterruptedException { //初始值,也能夠用自定義對象 int initialRef = 1; //初始版本 int initialStamp = 1; AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(initialRef, initialStamp); System.out.println("initial ref=" + atomicStampedReference.getReference() + " and stamp=" + atomicStampedReference.getStamp()); //指望值 final int expectedRef = atomicStampedReference.getReference(); //新值 int newRef = atomicStampedReference.getReference() + 10; //指望版本 final int expectedStamp = atomicStampedReference.getStamp(); //新版本 int newStamp = atomicStampedReference.getStamp() + 1; Thread t1 = new Thread(() -> System.out.println("success:" + atomicStampedReference.compareAndSet(expectedRef, newRef, expectedStamp, newStamp))); t1.start(); t1.join(); System.out.println("ref=" + atomicStampedReference.getReference() + " and stamp=" + atomicStampedReference.getStamp()); Thread t2 = new Thread(() -> System.out.println("success:" + atomicStampedReference.compareAndSet(expectedRef, newRef, expectedStamp, newStamp))); t2.start(); t2.join(); System.out.println("ref=" + atomicStampedReference.getReference() + " and stamp=" + atomicStampedReference.getStamp()); } }
AtomicMarkableReference
AtomicStampedReference能夠知道,引用變量中途被更改了幾回。有時候,咱們並不關心引用變量更改了幾回,只是單純的關心是否更改過,因此就有了AtomicMarkableReference。
AtomicMarkableReference的惟一區別就是再也不用int標識引用,而是使用boolean變量——表示引用變量是否被更改過。
示例代碼
public class AtomicMarkableReferenceTest { public static void main(String[] args) throws InterruptedException { //初始值,也能夠用自定義對象 int initialRef = 1; //初始標識 boolean initialMark = false; AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference(initialRef, initialMark); System.out.println("initial ref=" + markableReference.getReference() + " and isMarked=" + markableReference.isMarked()); //指望值 int expectedRef = markableReference.getReference(); /** * 併發過程當中t1,t2兩個線程的指望值都是expectedRef,把值加10 * t1,t2兩個線程的指望標誌都是false,操做成功後置爲true * t1成功後標誌變爲true,因此與t2指望值不符,操做失敗 */ Thread t1 = new Thread(() -> System.out.println("success:" + markableReference.compareAndSet(expectedRef, markableReference.getReference() + 10, false, true))); t1.start(); t1.join(); System.out.println("ref=" + markableReference.getReference() + " and isMarked=" + markableReference.isMarked()); Thread t2 = new Thread(() -> System.out.println("success:" + markableReference.compareAndSet(expectedRef, markableReference.getReference() + 10, false, false))); t2.start(); t2.join(); System.out.println("ref=" + markableReference.getReference() + " and isMarked=" + markableReference.isMarked()); } }
DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder是JDK1.8新增的部分,是對AtomicLong等類的改進。好比LongAccumulator與LongAdder在高併發環境下比AtomicLong更高效。本文以LongAdder爲例,學習這些類。
API中是這麼介紹的:LongAdder中會維護一組(一個或多個)變量,這些變量加起來就是要以原子方式更新的long型變量。當更新方法add(long)在線程間競爭時,該組變量能夠動態增加以減緩競爭。方法sum()返回當前在維持總和的變量上的總和。與AtomicLong相比,LongAdder更多地用於收集統計數據,而不是細粒度的同步控制。在低併發環境下,二者性能很類似。但在高併發環境下,LongAdder有着明顯更高的吞吐量,可是有着更高的空間複雜度。
實現原理
與其餘原子類同樣,LongAdder也是基於CAS實現的。
LongAdder能夠代替AtomicLong嗎
固然不能。在上面已經提到,與AtomicLong相比,LongAdder更多地用於收集統計數據,而不是細粒度的同步控制。並且,LongAdder只提供了add(long)和decrement()方法,想要使用cas方法仍是要選擇AtomicLong。
DoubleAdder、LongAccumulator、DoubleAccumulator與LongAdder很類似,就很少作介紹了。