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