原子操作之 AtomicReference
1. 前言
今天為大家介紹原子操作之 AtomicReference。此工具位于 java.util.concurrent.atomic 包中。
本節(jié)先介紹什么是原子引用,接著展示 AtomicReference 工具類的最基本用法,之后給出 AtomicReference 工具類最常用的場合說明,然后通過簡單的編碼實(shí)現(xiàn)一個(gè)實(shí)際案例,最后帶領(lǐng)大家熟悉 AtomicReference 最常用的一些編程方法,讓大家進(jìn)一步加深對 AtomicReference 工具類的理解。
下面我們正式開始介紹吧。
2. 概念介紹
本節(jié)介紹的 AtomicReference 工具類直譯為 “原子引用”。原子操作的概念我們在之前的章節(jié)中已經(jīng)介紹過了,那什么是引用呢?
引用就是為對象另起一個(gè)名字,引用對象本身指向被引用對象,對引用對象的操作都會反映到被引用對象上。在 Java 中,引用對象本身存儲的是被引用對象的 “索引值”。如果對引用概念還是比較模糊,請查閱 Java 基礎(chǔ)語法知識復(fù)習(xí)。
AtomicReference 工具類和 AtomicInteger 工具類很相似,只是 AtomicInteger 工具類是對基本類型的原子封裝,而 AtomicReference 工具類是對引用類型的原子封裝。我們用一張?jiān)韴D展示其基本邏輯。
我們看下面 AtomicReference 工具類的基本用法。
3. 基本用法
// 由于AtomicReference是對一個(gè)對象引用的原子封裝,所以首先創(chuàng)建一個(gè)對象
Car car1 = new Car(100, 10);
// 接著使用構(gòu)造方法創(chuàng)建一個(gè) AtomicReference 對象。
AtomicReference<Car> atomicReference = new AtomicReference<>(car);
...
// 當(dāng)前如果是car1對象時(shí),以原子方式變更引用為car2對象,當(dāng)結(jié)果是true時(shí)則更新成功
Car car2 = new Car(200, 2);
Boolean bool = atomicReference.compareAndSet(car1, car2)
...
是不是很簡單,那 AtomicReference 在我們?nèi)粘?shí)踐中,到底應(yīng)該應(yīng)用在哪些場合比較合適呢?下面我們給出最常用的場景說明。
4. 常用場景
AtomicReference 和 AtomicInteger 非常類似,不同之處就在于 AtomicInteger 是對整數(shù)的封裝,且每次只能對一個(gè)整數(shù)進(jìn)行封裝,而 AtomicReference 則是對普通的對象引用的封裝,可將多個(gè)變量作為一個(gè)整體對象,操控多個(gè)屬性的原子性的并發(fā)類。
下面我們用 AtomicReference 工具類實(shí)現(xiàn)生活中汽車牌照競拍的例子:假設(shè)總共有 10 位客戶參與競拍,每位客戶只有一次競拍機(jī)會,競拍是資格競拍不以競拍價(jià)格為目的。請看下面的代碼。
5. 場景案例
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
// 代表待拍的車牌
private static CarLicenseTag carLicenseTag = new CarLicenseTag(80000);
// 創(chuàng)建一個(gè) AtomicReference 對象,對車牌對象做原子引用封裝
private static AtomicReference<CarLicenseTag> carLicenseTagAtomicReference = new AtomicReference<>(carLicenseTag);
public static void main(String[] args) {
// 定義5個(gè)客戶進(jìn)行競拍
for(int i=1; i<=5; i++) {
AuctionCustomer carAuctionCustomer = new AuctionCustomer(carLicenseTagAtomicReference, carLicenseTag, i);
// 開始競拍
new Thread(carAuctionCustomer).start();
}
}
}
/**
* 車牌
*/
public class CarLicenseTag {
// 每張車牌牌號事先是固定的
private String licenseTagNo = "滬X66666";
// 車牌的最新拍賣價(jià)格
private double price = 80000.00;
public CarLicenseTag(double price) {
this.price += price;
}
public String toString() {
return "CarLicenseTag{ licenseTagNo='" + licenseTagNo + ", price=" + price + '}';
}
}
每個(gè)客戶是如何動作呢,看下面的代碼。
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
public class AuctionCustomer implements Runnable {
private AtomicReference<CarLicenseTag> carLicenseTagReference;
private CarLicenseTag carLicenseTag;
private String customerNo;
public AuctionCustomer(AtomicReference<CarLicenseTag> carLicenseTagReference, CarLicenseTag carLicenseTag, int customerNo) {
this.carLicenseTagReference = carLicenseTagReference;
this.carLicenseTag = carLicenseTag;
this.customerNo = "第" + customerNo + "位客戶";
}
public void run() {
// 客戶競拍行為 (模擬競拍思考準(zhǔn)備時(shí)間4秒鐘)
try {
Thread.sleep(new Random().nextInt(4000));
} catch (Exception e) {}
// 舉牌更新最新的競拍價(jià)格
// 此處做原子引用更新
boolean bool = carLicenseTagReference.compareAndSet(carLicenseTag,
new CarLicenseTag(new Random().nextInt(1000)));
System.out.println("第" + customerNo + "位客戶競拍" + bool + " 當(dāng)前的競拍信息" + carLicenseTagReference.get().toString());
}
}
運(yùn)行后運(yùn)行結(jié)果如下。
...
第第1位客戶位客戶競拍true 當(dāng)前的競拍信息CarLicenseTag{ licenseTagNo='滬X66666, price=80405.0}
第第5位客戶位客戶競拍false 當(dāng)前的競拍信息CarLicenseTag{ licenseTagNo='滬X66666, price=80405.0}
第第3位客戶位客戶競拍false 當(dāng)前的競拍信息CarLicenseTag{ licenseTagNo='滬X66666, price=80405.0}
第第2位客戶位客戶競拍false 當(dāng)前的競拍信息CarLicenseTag{ licenseTagNo='滬X66666, price=80405.0}
第第4位客戶位客戶競拍false 當(dāng)前的競拍信息CarLicenseTag{ licenseTagNo='滬X66666, price=80405.0}
...
至此,大家對 AtomicReference 已經(jīng)有了初步的理解,接下來我們繼續(xù)豐富對 AtomicReference 工具類的認(rèn)識。
6. 核心方法介紹
除過上面代碼中使用的最基本的 AtomicReference (V)、compareAndSet (int, int)、get () 方法之外,我們還再介紹兩個(gè)方法的使用。下面逐個(gè)介紹。
- set () 方法
可以使用不帶參數(shù)的構(gòu)造方法構(gòu)造好對象后,再使用 set () 方法設(shè)置待封裝的對象。等價(jià)于使用 AtomicReference (V) 構(gòu)造方法。
- getAndSet () 方法
此方法以原子方式設(shè)置為給定值,并返回舊值。邏輯等同于先調(diào)用 get () 方法再調(diào)用 set () 方法。
7. 小結(jié)
本節(jié)通過一個(gè)簡單的例子,介紹了 AtomicReference 的基本用法。其實(shí)在 java.util.concurrent.atomic 包中還提供了更多更細(xì)場景的原子操作類,此包下的大部分工具類都是基于 CAS 原理實(shí)現(xiàn),正因?yàn)槿绱?,有很多相似之處,用法大同小異,希望大家在日常研發(fā)中多比較多總結(jié),早日掌握之。