Atomic包的作用:
方便程序員在多線程環境下,無鎖的進行原子操作
Atomic包核心:
Atomic包裏的類基本都是使用Unsafe實現的包裝類,核心操作是CAS原子操作
關於CAS
compare and swap,比較和替換技術,將預期值與當前變量的值比較(compare),如果相等則使用新值替換(swap)當前變量,否則不作操作;
現代CPU已廣泛支持CAS指令,如果不支持,那麼JVM將使用自旋鎖,與互斥鎖一樣,兩者都需先獲取鎖才能訪問共享資源,但互斥鎖會導致線程進入睡眠,而自旋鎖會一直循環等待直到獲取鎖;
另外,有一點需要注意的是CAS操作中的ABA問題,即將預期值與當前變量的值比較的時候,即使相等也不能保證變量沒有被修改過,因為變量可能由A變成B再變回A,解決該問題,可以給變量增加一個版本號,每次修改變量時版本號自增,比較的時候,同時比較變量的值和版本號即可
Atomic包主要提供四種原子更新方式
- 原子方式更新基本類型
- 原子方式更新數組
- 原子方式更新引用
- 原子方式更新字段
原子方式更新基本類型
以下三個類是以原子方式更新基本類型
- AtomicBoolean:原子更新布爾類型。
- AtomicInteger:原子更新整型。
- AtomicLong:原子更新長整型。
以AtomicInteger為例:
package cn.com.example.concurrent.atomic;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by Jack on 2017/1/7.
*/
public class AtomicIntegerTest extends Thread {
private AtomicInteger atomicInteger;
public AtomicIntegerTest(AtomicInteger atomicInteger) {
this.atomicInteger = atomicInteger;
}
@Override
public void run() {
int i = atomicInteger.incrementAndGet();
System.out.println("generated out number:" + i);
}
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger();
for (int i = 0; i < 10; i++) {//10個線程
new AtomicIntegerTest(counter).start();
}
}
}
輸出:
generated out number:1
generated out number:2
generated out number:3
generated out number:4
generated out number:5
generated out number:6
generated out number:7
generated out number:8
generated out number:9
generated out number:10
注意:Atomic包提供了三種基本類型的原子更新,剩餘的Java的基本類型還有char,float和double等,其更新方式可以參考AtomicBoolean的思路來現,AtomicBoolean是把boolean轉成整型再調用compareAndSwapInt進行CAS來實現的,類似的short和byte也可以轉成整形,float和double可以利用Float.floatToIntBits,Double.doubleToLongBits轉成整形和長整形進行相應處理
原子方式更新數組
以下三個類是以原子方式更新數組
- AtomicIntegerArray:原子更新整型數組裏的元素。
- AtomicLongArray:原子更新長整型數組裏的元素。
- AtomicReferenceArray:原子更新引用類型數組裏的元素
以AtomicIntegerArray為例,其方法與AtomicInteger很像,多了個數組下標索引
package cn.com.example.concurrent.atomic;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* Created by Jack on 2017/1/7.
*/
public class AtomicIntegerArrayTest {
private static int threadCount = 1000;
private static CountDownLatch countDown = new CountDownLatch(threadCount);
static int[] values = new int[10];
static AtomicIntegerArray ai = new AtomicIntegerArray(values);
private static class Counter implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 10; j++) {//所有元素+1
ai.getAndIncrement(j);
}
}
countDown.countDown();
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(new Counter());
}
for (int i = 0; i < threadCount; i++) {
threads[i].start();
}
countDown.await();
for (int i = 0; i < 10; i++) {
System.out.println(ai.get(i) + " ");
}
System.out.println();
for (int i = 0; i < 10; i++) {
System.out.println(values[i] + " ");
}
}
}
輸出:
100000
100000
100000
100000
100000
100000
100000
100000
100000
100000
0
0
0
0
0
0
0
0
0
0
需要注意的是,數組value通過構造方法傳遞進去,然後AtomicIntegerArray會將當前數組複製一份,所以當AtomicIntegerArray對內部的數組元素進行修改時,不會影響傳入的數組。
原子方式更新引用
以下三個類是以原子方式更新引用,與其它不同的是,更新引用可以更新多個變量,而不是一個變量
- AtomicReference:原子更新引用類型。
- AtomicReferenceFieldUpdater:原子更新引用類型裏的字段。
- AtomicMarkableReference:原子更新帶有標記位的引用類型。
以AtomicReference為例
package cn.com.example.concurrent.atomic;
import java.util.concurrent.atomic.AtomicReference;
/**
* Created by Jack on 2017/1/7.
*/
public class AtomicReferenceTest {
public static void main(String[] args) {
// 創建兩個Person對象,它們的id分別是101和102。
Person p1 = new Person(101);
Person p2 = new Person(102);
// 新建AtomicReference對象,初始化它的值為p1對象
AtomicReference ar = new AtomicReference(p1);
// 通過CAS設置ar。如果ar的值為p1的話,則將其設置為p2。
ar.compareAndSet(p1, p2);
Person p3 = (Person) ar.get();
System.out.println("p3 is " + p3);
System.out.println("p3.equals(p1)=" + p3.equals(p1));
}
}
class Person {
volatile long id;
public Person(long id) {
this.id = id;
}
public String toString() {
return "id:" + id;
}
}
輸出:
p3 is id:102
p3.equals(p1)=false
新建AtomicReference對象ar時,將它初始化為p1。
緊接着,通過CAS函數對它進行設置。如果ar的值為p1的話,則將其設置為p2。
最後,獲取ar對應的對象,並打印結果。p3.equals(p1)的結果為false,這是因為Person並沒有覆蓋equals()方法,而是採用繼承自Object.java的equals()方法;而Object.java中的equals()實際上是調用"=="去比較兩個對象,即比較兩個對象的地址是否相等。
原子方式更新字段
以下三個類是以原子方式更新字段
- AtomicIntegerFieldUpdater:原子更新整型字段的更新器。
- AtomicLongFieldUpdater:原子更新長整型字段的更新器。
- AtomicStampedReference:原子更新帶有版本號的引用類型,用於解決使用CAS進行原子更新時,可能出現的ABA問題。
以AtomicIntegerFieldUpdater為例
package cn.com.example.concurrent.atomic;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* Created by Jack on 2017/1/7.
*/
public class AtomicIntegerFieldUpdaterTest {
// 創建原子更新器,並設置需要更新的對象類和對象的屬性
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");
public static void main(String[] args) throws InterruptedException {
// 設置柯南的年齡是10歲
User conan = new User("conan", 10);
// 柯南長了一歲,但是仍然會輸出舊的年齡
System.out.println(a.getAndIncrement(conan));
// 輸出柯南現在的年齡
System.out.println(a.get(conan));
}
public static class User {
private String name;
public volatile int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}
輸出:
10
11
注意: old 需要聲明為 volatile