Atomic包核心
Atomic包里的類基本都是使用Unsafe實(shí)現(xiàn)的包裝類丹皱,核心操作是CAS原子操作。
compare and swap宋税,比較和替換技術(shù)摊崭,將預(yù)期值與當(dāng)前變量的值比較(compare),如果相等則使用新值替換(swap)當(dāng)前變量杰赛,否則不作操作呢簸。
現(xiàn)代CPU已廣泛支持CAS指令,如果不支持乏屯,那么JVM將使用自旋鎖根时,與互斥鎖一樣,兩者都需先獲取鎖才能訪問(wèn)共享資源辰晕,但互斥鎖會(huì)導(dǎo)致線程進(jìn)入睡眠蛤迎,而自旋鎖會(huì)一直循環(huán)等待直到獲取鎖。
另外含友,有一點(diǎn)需要注意的是CAS操作中的ABA問(wèn)題替裆,即將預(yù)期值與當(dāng)前變量的值比較的時(shí)候,即使相等也不能保證變量沒(méi)有被修改過(guò)窘问,因?yàn)樽兞靠赡苡葾變成B再變回A,解決該問(wèn)題辆童,可以給變量增加一個(gè)版本號(hào),每次修改變量時(shí)版本號(hào)自增南缓,比較的時(shí)候胸遇,同時(shí)比較變量的值和版本號(hào)即可。
Atomic包主要提供四種原子更新方式
- 原子方式更新基本類型
- 原子方式更新數(shù)組
- 原子方式更新引用
- 原子方式更新字段
原子方式更新基本類型
包括:AtomicBoolean:原子更新布爾類型汉形;AtomicInteger:原子更新整型纸镊;AtomicLong:原子更新長(zhǎng)整型。
下面以AtomicInteger舉個(gè)例子:
// 請(qǐng)求總數(shù)
public static int clientTotal = 5000;
// 同時(shí)并發(fā)執(zhí)行的線程數(shù)
public static int threadTotal = 200;
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count.get());
}
private static void add() {
count.incrementAndGet();
// count.getAndIncrement();
}
注意:Atomic包提供了三種基本類型的原子更新概疆,剩余的Java的基本類型還有char逗威,float和double等,其更新方式可以參考AtomicBoolean的思路來(lái)現(xiàn)岔冀,AtomicBoolean是把boolean轉(zhuǎn)成整型再調(diào)用compareAndSwapInt進(jìn)行CAS來(lái)實(shí)現(xiàn)的凯旭,類似的short和byte也可以轉(zhuǎn)成整形,float和double可以利用Float.floatToIntBits使套,Double.doubleToLongBits轉(zhuǎn)成整形和長(zhǎng)整形進(jìn)行相應(yīng)處理罐呼。
原子方式更新數(shù)組
以下三個(gè)類是以原子方式更新數(shù)組:
AtomicIntegerArray:原子更新整型數(shù)組里的元素。
AtomicLongArray:原子更新長(zhǎng)整型數(shù)組里的元素侦高。
AtomicReferenceArray:原子更新引用類型數(shù)組里的元素嫉柴。
以AtomicIntegerArray為例,其方法與AtomicInteger很像奉呛,多了個(gè)數(shù)組下標(biāo)索引:
static int[] valueArr = new int[] { 1, 2 };
//AtomicIntegerArray內(nèi)部會(huì)拷貝一份數(shù)組
static AtomicIntegerArray ai = new AtomicIntegerArray(valueArr);
public static void main(String[] args) {
ai.getAndSet(0, 3);
//不會(huì)修改原始數(shù)組value
System.out.println(ai.get(0));
System.out.println(valueArr[0]);
}
原子方式更新引用
以下三個(gè)類是以原子方式更新引用计螺,與其它不同的是夯尽,更新引用可以更新多個(gè)變量,而不是一個(gè)變量:
AtomicReference:原子更新引用類型登馒。
AtomicReferenceFieldUpdater:原子更新引用類型里的字段匙握。
AtomicMarkableReference:原子更新帶有標(biāo)記位的引用類型。
以AtomicReference為例:
public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();
public static void main(String[] args) {
User user = new User("conan", 15);
atomicUserRef.set(user);
User updateUser = new User("Shinichi", 17);
atomicUserRef.compareAndSet(user, updateUser);
System.out.println(atomicUserRef.get().getName());
System.out.println(atomicUserRef.get().getOld());
}
static class User {
private String name;
private int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
原子方式更新字段
以下三個(gè)類是以原子方式更新字段陈轿,
AtomicIntegerFieldUpdater:原子更新整型字段的更新器圈纺。
AtomicLongFieldUpdater:原子更新長(zhǎng)整型字段的更新器。
AtomicStampedReference:原子更新帶有版本號(hào)的引用類型麦射,用于解決使用CAS進(jìn)行原子更新時(shí)赠堵,可能出現(xiàn)的ABA問(wèn)題。
以AtomicIntegerFieldUpdater為例:
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater
.newUpdater(User.class, "old");
public static void main(String[] args) {
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;
//注意需要用volatile修飾
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;
}
}
LongAdder
jdk8中的LongAdder在并發(fā)競(jìng)爭(zhēng)激烈的情況下法褥,效率更高。