【Java】原子類(lèi)
點(diǎn)擊關(guān)注,與你共同成長(zhǎng)!

【Java】原子類(lèi)
前言
保證線程安全是 Java 并發(fā)編程必須要解決的重要問(wèn)題。Java 從原子性、可見(jiàn)性、有序性這三大特性入手,確保多線程的數(shù)據(jù)一致性。
確保線程安全最常見(jiàn)的做法是利用鎖機(jī)制( Lock、sychronized)來(lái)對(duì)共享數(shù)據(jù)做互斥同步,這樣在同一個(gè)時(shí)刻,只有一個(gè)線程可以執(zhí)行某個(gè)方法或者某個(gè)代碼塊,那么操作必然是原子性的,線程安全的。互斥同步最主要的問(wèn)題是線程阻塞和喚醒所帶來(lái)的性能問(wèn)題;volatile是輕量級(jí)的鎖(自然比普通鎖性能要好),它保證了共享變量在多線程中的可見(jiàn)性,但無(wú)法保證原子性。所以,它只能在一些特定場(chǎng)景下使用;為了兼顧原子性以及鎖帶來(lái)的性能問(wèn)題,Java 引入了 CAS (主要體現(xiàn)在 Unsafe類(lèi))來(lái)實(shí)現(xiàn)非阻塞同步(也叫樂(lè)觀鎖),并基于 CAS ,提供了一套原子工具類(lèi)。
原子類(lèi)
原子變量類(lèi) 比鎖的粒度更細(xì),更輕量級(jí),并且對(duì)于在多處理器系統(tǒng)上實(shí)現(xiàn)高性能的并發(fā)代碼來(lái)說(shuō)是非常關(guān)鍵的。原子變量將發(fā)生競(jìng)爭(zhēng)的范圍縮小到單個(gè)變量上。
原子變量類(lèi)可以分為 4 類(lèi)
基本類(lèi)型 AtomicBoolean:布爾類(lèi)型原子類(lèi);AtomicInteger:整型原子類(lèi);AtomicLong:長(zhǎng)整型原子類(lèi)。引用類(lèi)型 AtomicReference:引用類(lèi)型原子類(lèi);AtomicMarkableReference:帶有標(biāo)記位的引用類(lèi)型原子類(lèi);AtomicStampedReference:帶有版本號(hào)的引用類(lèi)型原子類(lèi)。數(shù)組類(lèi)型 AtomicIntegerArray:整形數(shù)組原子類(lèi);AtomicLongArray:長(zhǎng)整型數(shù)組原子類(lèi);AtomicReferenceArray:引用類(lèi)型數(shù)組原子類(lèi)。屬性更新器類(lèi)型 AtomicIntegerFieldUpdater:整型字段的原子更新器;AtomicLongFieldUpdater:長(zhǎng)整型字段的原子更新器;AtomicReferenceFieldUpdater:原子更新引用類(lèi)型里的字段。
基本類(lèi)型
AtomicBoolean:布爾類(lèi)型原子類(lèi);AtomicInteger:整型原子類(lèi);AtomicLong:長(zhǎng)整型原子類(lèi)。
線程不安全
package cn.com.codingce.juc.atomic;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
static int value2 = 0;
public static void main(String[] args) {
System.out.println("==============ordinaryMethod==============");
ExecutorService executor3 = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executor3.execute(()->{
System.out.println(Thread.currentThread().getName() + ", value2: " + value2 + " ");
value2++;
});
}
executor3.shutdown();
}
}
output
==============Method==============
pool-1-thread-3, value2: 0
pool-1-thread-2, value2: 0
pool-1-thread-3, value2: 2
pool-1-thread-4, value2: 0
pool-1-thread-1, value2: 0
pool-1-thread-3, value2: 3
pool-1-thread-2, value2: 2
pool-1-thread-5, value2: 1
pool-1-thread-4, value2: 5
pool-1-thread-1, value2: 5
synchronized案例
package cn.com.codingce.juc.atomic;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
static int value1 = 0;
public static void main(String[] args) {
System.out.println("==============synchronizedMethod==============");
ExecutorService executor2 = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executor2.execute(AtomicIntegerExample::synchronizedMethod);
}
executor2.shutdown();
}
public static synchronized void synchronizedMethod() {
System.out.println(Thread.currentThread().getName() + ", value1: " + value1 + " ");
value1++;
}
}
output
==============synchronizedMethod==============
pool-1-thread-1, value1: 0
pool-1-thread-3, value1: 1
pool-1-thread-2, value1: 2
pool-1-thread-1, value1: 3
pool-1-thread-4, value1: 4
pool-1-thread-3, value1: 5
pool-1-thread-4, value1: 6
pool-1-thread-1, value1: 7
pool-1-thread-5, value1: 8
pool-1-thread-2, value1: 9
AtomicInteger 案例
package cn.com.codingce.juc.atomic;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
static AtomicInteger value = new AtomicInteger(0);
public static void main(String[] args) {
System.out.println("==============AtomicInteger==============");
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executor.execute(AtomicIntegerExample::atomicIntegerMethod);
}
executor.shutdown();
}
public static synchronized void atomicIntegerMethod() {
System.out.println(Thread.currentThread().getName() + ", value: " + value.get() + " ");
value.incrementAndGet();
}
}
output
==============AtomicInteger==============
pool-1-thread-2, value: 0
pool-1-thread-1, value: 1
pool-1-thread-3, value: 2
pool-1-thread-5, value: 3
pool-1-thread-1, value: 4
pool-1-thread-2, value: 5
pool-1-thread-4, value: 6
pool-1-thread-1, value: 7
pool-1-thread-5, value: 8
pool-1-thread-3, value: 9
AtomicInteger實(shí)現(xiàn)可以看到如下定義
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
value- value 屬性使用volatile修飾,使得對(duì) value 的修改在并發(fā)環(huán)境下對(duì)所有線程可見(jiàn);valueOffset- value 屬性的偏移量,通過(guò)這個(gè)偏移量可以快速定位到 value 字段,這個(gè)是實(shí)現(xiàn) AtomicInteger 的關(guān)鍵;unsafe- Unsafe 類(lèi)型的屬性,它為 AtomicInteger 提供了 CAS 操作。
引用類(lèi)型
Java 數(shù)據(jù)類(lèi)型分為 基本數(shù)據(jù)類(lèi)型 和 引用數(shù)據(jù)類(lèi)型 兩大類(lèi)。
AtomicReference:引用類(lèi)型原子類(lèi);AtomicMarkableReference:帶有標(biāo)記位的引用類(lèi)型原子類(lèi);AtomicStampedReference:帶有版本號(hào)的引用類(lèi)型原子類(lèi)。
AtomicStampedReference類(lèi)在引用類(lèi)型原子類(lèi)中,徹底地解決了 ABA 問(wèn)題,其它的 CAS 能力與另外兩個(gè)類(lèi)相近,所以最具代表性。
基于 AtomicReference 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的自旋鎖
package cn.com.codingce.juc.atomic;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceExample {
public static int ticket = 10;
public static void main(String[] args) {
threadSafe();
}
private static void threadSafe() {
SpinLock lock = new SpinLock();
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executorService.execute(new MyThread(lock));
}
executorService.shutdown();
}
/**
* 基于 {@link AtomicReference} 實(shí)現(xiàn)的簡(jiǎn)單自旋鎖
*/
static class SpinLock {
private AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
Thread current = Thread.currentThread();
while (!atomicReference.compareAndSet(null, current)) ;
}
public void unlock() {
Thread current = Thread.currentThread();
while (!atomicReference.compareAndSet(current, null)) ;
}
}
static class MyThread implements Runnable {
private SpinLock lock;
public MyThread(SpinLock lock) {
this.lock = lock;
}
@Override
public void run() {
while (ticket > 0) {
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " 賣(mài)出了第 " + ticket + " 張票");
ticket--;
}
lock.unlock();
}
}
}
}
output
pool-1-thread-1 賣(mài)出了第 10 張票
pool-1-thread-3 賣(mài)出了第 9 張票
pool-1-thread-2 賣(mài)出了第 8 張票
pool-1-thread-1 賣(mài)出了第 7 張票
pool-1-thread-2 賣(mài)出了第 6 張票
pool-1-thread-1 賣(mài)出了第 5 張票
pool-1-thread-3 賣(mài)出了第 4 張票
pool-1-thread-1 賣(mài)出了第 3 張票
pool-1-thread-2 賣(mài)出了第 2 張票
pool-1-thread-1 賣(mài)出了第 1 張票
原子類(lèi)的實(shí)現(xiàn)基于 CAS 機(jī)制,而 CAS 存在 ABA 問(wèn)題,為了解決 ABA 問(wèn)題,才有了 AtomicMarkableReference 和 AtomicStampedReference。
AtomicMarkableReference 使用一個(gè)布爾值作為標(biāo)記,修改時(shí)在 true / false 之間切換。這種策略不能根本上解決 ABA 問(wèn)題,但是可以降低 ABA 發(fā)生的幾率。常用于緩存或者狀態(tài)描述這樣的場(chǎng)景。
package cn.com.codingce.juc.atomic;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;
public class AtomicMarkableReferenceExample {
private final static String INIT_STR = "后端碼匠";
public static void main(String[] args) throws InterruptedException {
final AtomicMarkableReference<String> amr = new AtomicMarkableReference<>(INIT_STR, false);
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
executorService.execute(() -> {
System.out.println("當(dāng)前線程: " + Thread.currentThread().getName());
try {
Thread.sleep(Math.abs((int) (Math.random() * 100)));
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
if (amr.compareAndSet(INIT_STR, name, amr.isMarked(), !amr.isMarked())) {
System.out.println(Thread.currentThread().getName() + " 修改了對(duì)象!");
System.out.println("新的對(duì)象為:" + amr.getReference());
}
});
}
executorService.shutdown();
System.out.println(executorService.awaitTermination(3, TimeUnit.SECONDS));
;
}
}
output
當(dāng)前線程: pool-1-thread-1
當(dāng)前線程: pool-1-thread-3
當(dāng)前線程: pool-1-thread-2
pool-1-thread-3 修改了對(duì)象!
新的對(duì)象為:pool-1-thread-3
true
AtomicStampedReference 使用一個(gè)整型值做為版本號(hào),每次更新前先比較版本號(hào),如果一致,才進(jìn)行修改。通過(guò)這種策略,可以根本上解決 ABA 問(wèn)題。
package cn.com.codingce.juc.atomic;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceExample {
private final static String INIT_REF = "后端碼匠";
private final static AtomicStampedReference<String> asr = new AtomicStampedReference<>(INIT_REF, 0);
public static void main(String[] args) throws InterruptedException {
System.out.println("初始對(duì)象為:" + asr.getReference());
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
executorService.execute(() -> {
System.out.println("當(dāng)前線程: " + Thread.currentThread().getName());
try {
Thread.sleep(Math.abs((int) (Math.random() * 100)));
} catch (InterruptedException e) {
e.printStackTrace();
}
final int stamp = asr.getStamp();
if (asr.compareAndSet(INIT_REF, Thread.currentThread().getName(), stamp, stamp + 1)) {
System.out.println(Thread.currentThread().getName() + " 修改了對(duì)象!");
System.out.println("新的對(duì)象為:" + asr.getReference());
}
});
}
executorService.shutdown();
System.out.println(executorService.awaitTermination(3, TimeUnit.SECONDS));
}
}
output
初始對(duì)象為:后端碼匠
當(dāng)前線程: pool-1-thread-1
當(dāng)前線程: pool-1-thread-2
當(dāng)前線程: pool-1-thread-3
pool-1-thread-2 修改了對(duì)象!
新的對(duì)象為:pool-1-thread-2
true
數(shù)組類(lèi)型
AtomicIntegerArray:整形數(shù)組原子類(lèi);AtomicLongArray:長(zhǎng)整型數(shù)組原子類(lèi);AtomicReferenceArray:引用類(lèi)型數(shù)組原子類(lèi)。
AtomicIntegerArray示例
package cn.com.codingce.juc.atomic;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayExample {
private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
public static void main(final String[] arguments) throws InterruptedException {
System.out.println("Init Values: ");
for (int i = 0; i < atomicIntegerArray.length(); i++) {
atomicIntegerArray.set(i, i);
System.out.print(atomicIntegerArray.get(i) + " ");
}
System.out.println();
System.out.println("========================================");
Thread t1 = new Thread(new Increment());
Thread t2 = new Thread(new Compare());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final Values: ");
for (int i = 0; i < atomicIntegerArray.length(); i++) {
System.out.print(atomicIntegerArray.get(i) + " ");
}
System.out.println();
}
static class Increment implements Runnable {
@Override
public void run() {
for (int i = 0; i < atomicIntegerArray.length(); i++) {
int value = atomicIntegerArray.incrementAndGet(i);
System.out.println(Thread.currentThread().getName() + ", index = " + i + ", value = " + value);
}
}
}
static class Compare implements Runnable {
@Override
public void run() {
for (int i = 0; i < atomicIntegerArray.length(); i++) {
boolean swapped = atomicIntegerArray.compareAndSet(i, 1, 100);
if (swapped) {
System.out.println(Thread.currentThread().getName() + " swapped, index = " + i + ", value = 3");
}
}
}
}
}
output
Init Values:
0 1 2 3 4 5 6 7 8 9
========================================
Thread-0, index = 0, value = 1
Thread-0, index = 1, value = 2
Thread-1 swapped, index = 0, value = 3
Thread-0, index = 2, value = 3
Thread-0, index = 3, value = 4
Thread-0, index = 4, value = 5
Thread-0, index = 5, value = 6
Thread-0, index = 6, value = 7
Thread-0, index = 7, value = 8
Thread-0, index = 8, value = 9
Thread-0, index = 9, value = 10
Final Values:
100 2 3 4 5 6 7 8 9 10
屬性更新器類(lèi)型
更新器類(lèi)支持基于反射機(jī)制的更新字段值的原子操作。
AtomicIntegerFieldUpdater:整型字段的原子更新器。AtomicLongFieldUpdater:長(zhǎng)整型字段的原子更新器。AtomicReferenceFieldUpdater:原子更新引用類(lèi)型里的字段。
這些類(lèi)的使用有一定限制:
因?yàn)閷?duì)象的屬性修改類(lèi)型原子類(lèi)都是抽象類(lèi),所以每次使用都必須使用靜態(tài)方法 newUpdater()創(chuàng)建一個(gè)更新器,并且需要設(shè)置想要更新的類(lèi)和屬性;字段必須是 volatile類(lèi)型的;不能作用于靜態(tài)變量( static);不能作用于常量( final)。
package cn.com.codingce.juc.atomic;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class AtomicReferenceFieldUpdaterExample {
static User user = new User("歡迎關(guān)注");
static AtomicReferenceFieldUpdater<User, String> updater = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executorService.execute(new MyThread());
}
executorService.shutdown();
}
static class MyThread implements Runnable {
@Override
public void run() {
if (updater.compareAndSet(user, "歡迎關(guān)注", "后端碼匠")) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 已修改 name = " + user.getName());
} else {
System.out.println(Thread.currentThread().getName() + " 已被其他線程修改");
}
}
}
static class User {
volatile String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public User setName(String name) {
this.name = name;
return this;
}
}
}
output
pool-1-thread-2 已被其他線程修改
pool-1-thread-3 已被其他線程修改
pool-1-thread-2 已被其他線程修改
pool-1-thread-3 已被其他線程修改
pool-1-thread-1 已修改 name = 后端碼匠
方案對(duì)比
Atomic|synchronized
1、背后原理的不同
原子類(lèi):它保證線程安全的原理是利用了 CAS 操作。從這一點(diǎn)上看,雖然原子類(lèi)和 synchronized 都能保證線程安全,但是其實(shí)現(xiàn)原理是大有不同的;
synchronized:背后的 monitor 鎖,也就是 synchronized 原理,同步方法和同步代碼塊的背后原理會(huì)有少許差異,但總體思想是一致的:在執(zhí)行同步代碼之前,需要首先獲取到 monitor 鎖,執(zhí)行完畢后,再釋放鎖。2、使用范圍的不同
原子類(lèi):它的使用范圍是比較局限的。因?yàn)橐粋€(gè)原子類(lèi)僅僅是一個(gè)對(duì)象,不夠靈活;
synchronized 它的使用范圍要廣泛得多。比如說(shuō) synchronized 既可以修飾一個(gè)方法,又可以修飾一段代碼,相當(dāng)于可以根據(jù)我們的需要,非常靈活地去控制它的應(yīng)用范圍;
所以?xún)H有少量的場(chǎng)景,例如計(jì)數(shù)器等場(chǎng)景,可以使用原子類(lèi)。而在其他更多的場(chǎng)景下,如果原子類(lèi)不適用,那么就可以考慮用 synchronized 來(lái)解決這個(gè)問(wèn)題。
3、粒度的區(qū)別
原子類(lèi):原子變量的粒度是比較小的,它可以把競(jìng)爭(zhēng)范圍縮小到變量級(jí)別;
synchronized:通常情況下,synchronized 鎖的粒度都要大于原子變量的粒度;
如果只把一行代碼用 synchronized 給保護(hù)起來(lái)的話(huà),有一點(diǎn)殺雞焉用牛刀的感覺(jué)。
4、性能的區(qū)別,同時(shí)也是悲觀鎖和樂(lè)觀鎖的區(qū)別
因?yàn)?synchronized 是一種典型的悲觀鎖,而原子類(lèi)恰恰相反,它利用的是樂(lè)觀鎖。所以,在比較 synchronized 和 AtomicInteger 的時(shí)候,其實(shí)也就相當(dāng)于比較了悲觀鎖和樂(lè)觀鎖的區(qū)別;
從性能上來(lái)考慮的話(huà),悲觀鎖的操作相對(duì)來(lái)講是比較重量級(jí)的。因?yàn)?synchronized 在競(jìng)爭(zhēng)激烈的情況下,會(huì)讓拿不到鎖的線程阻塞,而原子類(lèi)是永遠(yuǎn)不會(huì)讓線程阻塞的。不過(guò),雖然 synchronized 會(huì)讓線程阻塞,但是這并不代表它的性能就比原子類(lèi)差;
悲觀鎖的開(kāi)銷(xiāo)是固定的,也是一勞永逸的。隨著時(shí)間的增加,這種開(kāi)銷(xiāo)并不會(huì)線性增長(zhǎng),而樂(lè)觀鎖雖然在短期內(nèi)的開(kāi)銷(xiāo)不大,但是隨著時(shí)間的增加,它的開(kāi)銷(xiāo)也是逐步上漲的
所以從性能的角度考慮,它們沒(méi)有一個(gè)孰優(yōu)孰劣的關(guān)系,而是要區(qū)分具體的使用場(chǎng)景。在競(jìng)爭(zhēng)非常激烈的情況下,推薦使用 synchronized;而在競(jìng)爭(zhēng)不激烈的情況下,使用原子類(lèi)會(huì)得到更好的效果。
注意
synchronized 的性能隨著 JDK 的升級(jí),也得到了不斷的優(yōu)化。synchronized 會(huì)從無(wú)鎖升級(jí)到偏向鎖,再升級(jí)到輕量級(jí)鎖,最后才會(huì)升級(jí)到讓線程阻塞的重量級(jí)鎖。因此synchronized 在競(jìng)爭(zhēng)不激烈的情況下,性能也是不錯(cuò)的,不需要“談鎖色變”。
Unsafe類(lèi)
實(shí)際上Atomic包里的類(lèi)基本都是使用Unsafe實(shí)現(xiàn)的包裝類(lèi)。也就是上面的原子類(lèi)實(shí)現(xiàn)過(guò)程中都會(huì)用到Unsafe類(lèi)。Java中的Unsafe類(lèi)提供了類(lèi)似C++手動(dòng)管理內(nèi)存的能力。Unsafe類(lèi),全限定名是sun.misc.Unsafe,從名字可以看出來(lái)這個(gè)類(lèi)對(duì)普通程序員來(lái)說(shuō)是“危險(xiǎn)”的,一般應(yīng)用開(kāi)發(fā)者不會(huì)用到這個(gè)類(lèi)。Unsafe類(lèi)是"final"的,不允許繼承。且構(gòu)造函數(shù)是private的,無(wú)法在外部對(duì)其進(jìn)行實(shí)例化。
Unsafe的典型應(yīng)用
堆外內(nèi)存操作。DirectByteBuffer是Java用于實(shí)現(xiàn)堆外內(nèi)存的一個(gè)重要類(lèi),通常用在通信過(guò)程中做緩沖池,如在Netty、MINA等NIO框架中應(yīng)用廣泛;DirectByteBuffer對(duì)于堆外內(nèi)存的創(chuàng)建、 使用、銷(xiāo)毀等邏輯均由Unsafe提供的堆外內(nèi)存API來(lái)實(shí)現(xiàn); ReentrantLock、Atomic等API通過(guò)CAS修改state等等,底層用的也是Unsafe; 線程調(diào)度:如LockSupport.park()和LockSupport.unpark()實(shí)現(xiàn)線程的阻塞和喚醒。而 LockSupport的park、unpark方法實(shí)際是調(diào)用Unsafe的park、unpark方式來(lái)實(shí)現(xiàn); 內(nèi)存屏障,通過(guò)Unsafe的loadFence方法加入一個(gè)內(nèi)存屏障,目的是避免指令重排。

以上,便是今天的分享,希望大家喜歡,覺(jué)得內(nèi)容不錯(cuò)的,歡迎「分享」「贊」或者點(diǎn)擊「在看」支持,謝謝各位。
