一、寫(xiě)在開(kāi)頭 在前面的博文中我們學(xué)習(xí)了volatile關(guān)鍵字,知道了它可以保證有序性和可見(jiàn)性,但無(wú)法保障原子性,結(jié)局原子性問(wèn)題推薦使用synchronized、Lock或者AtomicInteger;我們還學(xué)習(xí)過(guò)CAS算法,在那篇博文中我們同樣也提及atomic。那么今天,我們就來(lái)好好學(xué)一學(xué)Atom
在之前的博文中,我們已經(jīng)學(xué)習(xí)了volatile關(guān)鍵字,了解到它可以保證有序性和可見(jiàn)性,但無(wú)法保障原子性。解決原子性問(wèn)題時(shí),推薦使用synchronized、Lock或者AtomicInteger。我們還學(xué)習(xí)過(guò)CAS算法,提及了atomic。今天,我們將深入學(xué)習(xí)Atomic原子庫(kù),這是一個(gè)基于CAS算法實(shí)現(xiàn)的高效并發(fā)工具庫(kù)。
并發(fā)包java.util.concurrent的原子類都存放在java.util.concurrent.atomic中。如下圖所示:
Atomic翻譯為“原子”,何為原子?在化學(xué)領(lǐng)域的原子被認(rèn)為是構(gòu)成化學(xué)反應(yīng)的最小微觀粒子,是不可分割的最小單位。偉大的Doug Lea大師,將并發(fā)的一些類以此單詞開(kāi)頭命名,一語(yǔ)中的!
我們根據(jù)操作的數(shù)據(jù)類型可以將JUC包中的原子類做如下的4種劃分:
原子操作的基本類型主要可分為:
這三種方式用法幾乎相同,都是以原子更新的方式操作基本類型,我們?cè)谶@里以AtomicInteger為例看一下它的使用與原理。
1)AtomicInteger的常用方法
public final int get() //獲取當(dāng)前的值
public final int getAndSet(int newValue)//獲取當(dāng)前的值,并設(shè)置為newValue
public final int getAndIncrement()//獲取當(dāng)前的值,并自增
public final int incrementAndGet()//增加 1,并獲取新值,注意與上面方法區(qū)分
public final int getAndDecrement() //獲取當(dāng)前的值,并自減
public final int getAndAdd(int delta) //獲取當(dāng)前的值,并加上預(yù)期的值delta
boolean compareAndSet(int expect, int update) //如果輸入的數(shù)值等于預(yù)期值,則以原子方式將該值設(shè)置為輸入值(update)
public final void lazySet(int newValue)//最終設(shè)置為newValue,使用 lazySet 設(shè)置之后可能導(dǎo)致其他線程在之后的一小段時(shí)間內(nèi)還是可以讀到舊的值.
2)AtomicInteger的使用案例
public class Test {
public static void main(String[] args) {
//臨時(shí)值
int temvalue = 0;
AtomicInteger i = new AtomicInteger(0);
temvalue = i.getAndSet(3);
System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:0; i:3
temvalue = i.getAndIncrement();
System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:3; i:4
temvalue = i.getAndAdd(5);
System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:4; i:9
temvalue = i.incrementAndGet();
System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:10; i:10
}
}
這里需要注意的一點(diǎn)是getAndIncrement()方法與incrementAndGet(),一個(gè)先獲值后自增,一個(gè)先自增后獲值。
3)AtomicInteger的底層原理
我們以getAndIncrement()為例,去跟入它的底層代碼會(huì)發(fā)現(xiàn),其內(nèi)部是通過(guò)調(diào)用UnSafe類的靜態(tài)方法getUnsafe實(shí)現(xiàn)的。UnSafe類在CAS算法的時(shí)候有提及,后面我們?cè)賳为?dú)學(xué)習(xí)它,其底層是通過(guò)CAS,原子性的進(jìn)行增加值。
public final int getAndIncrement() {
// 使用Unsafe類中的getAndAddInt方法原子地增加AtomicInteger的當(dāng)前值
// 第一個(gè)參數(shù)this是AtomicInteger的當(dāng)前實(shí)例
// 第二個(gè)參數(shù)valueOffset是一個(gè)偏移量,它指示在AtomicInteger對(duì)象中的哪個(gè)位置可以找到實(shí)際的int值
// 第三個(gè)參數(shù)1表示要加到當(dāng)前值上的值(即增加的值)
// 此方法返回的是增加前的原始值
return unsafe.getAndAddInt(this, valueOffset, 1);
}
Unsafe
類是Java中的一個(gè)特殊類,用于執(zhí)行低級(jí)、不安全的操作。getAndIncrement方法就是利用了Unsafe類提供的CAS(Compare-And-Swap)操作來(lái)實(shí)現(xiàn)原子的increment操作。CAS是一種常用的無(wú)鎖技術(shù),允許在多線程環(huán)境中原子地更新值。
原子操作根據(jù)數(shù)組類型,可以分為如下幾種:
這三種同樣很類似,我們以AtomicIntegerArray為例來(lái)介紹一下。
1)AtomicIntegerArray的常用方法
public final int get(int i) //獲取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的當(dāng)前的值,并將其設(shè)置為新值:newValue
public final int getAndIncrement(int i)//獲取 index=i 位置元素的值,并讓該位置的元素自增
public final int getAndDecrement(int i) //獲取 index=i 位置元素的值,并讓該位置的元素自減
public final int getAndAdd(int i, int delta) //獲取 index=i 位置元素的值,并加上預(yù)期的值
boolean compareAndSet(int i, int expect, int update) //如果輸入的數(shù)值等于預(yù)期值,則以原子方式將 index=i 位置的元素值設(shè)置為輸入值(update)
public final void lazySet(int i, int newValue)//最終 將index=i 位置的元素設(shè)置為newValue,使用 lazySet 設(shè)置之后可能導(dǎo)致其他線程在之后的一小段時(shí)間內(nèi)還是可以讀到舊的值.
2)AtomicIntegerArray的使用案例
public class Test {
public static void main(String[] args) {
int temvalue = 0;
int[] nums = { 1, 2, 3, 4, 5, 6 };
AtomicIntegerArray i = new AtomicIntegerArray(nums);
for (int j = 0; j < nums.length; j++) {
System.out.print(i.get(j));
}
System.out.println();
temvalue = i.getAndSet(0, 2);
System.out.println("temvalue:" + temvalue + "; i:" + i);
temvalue = i.getAndIncrement(0);
System.out.println("temvalue:" + temvalue + "; i:" + i);
temvalue = i.getAndAdd(0, 5);
System.out.println("temvalue:" + temvalue + "; i:" + i);
}
}
輸出:
123456
temvalue:1; i:[2, 2, 3, 4, 5, 6]
temvalue:2; i:[3, 2, 3, 4, 5, 6]
temvalue:3; i:[8, 2, 3, 4, 5, 6]
除了如上的2種原子類外,atomic包中還提供了引用類型原子類。大概為如下幾種:
常用方法又上述兩種類型一致,這里不再贅述,我們直接寫(xiě)一個(gè)demo感受一下它的使用吧。
public class TestAtomicReference {
private static AtomicReference reference = new AtomicReference<>();
public static void main(String[] args) {
User user1 = new User("小明", 18);
reference.set(user1);
User user2 = new User("小華",20);
User user = reference.getAndSet(user2);
System.out.println(user);
System.out.println(reference.get());
}
static class User {
private String userName;
private int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
}
輸出:
User{userName='小明', age=18}
User{userName='小華', age=20}
除了原子更新對(duì)象(引用類型)外,atomic中還提供了更新對(duì)象的屬性字段的原子類:
如果想要原子的更新對(duì)象的屬性,實(shí)現(xiàn)起來(lái)較上面幾種類型略微復(fù)雜一下,大概分為兩步:
步驟1??
通過(guò)靜態(tài)方法newUpdater創(chuàng)建一個(gè)更新器,并且設(shè)置想要更新的類和字段;
步驟2??
字段必須使用public volatile進(jìn)行修飾;
以AtomicIntegerFieldUpdater為例,我們寫(xiě)一個(gè)測(cè)試類感受一下。
public class TestAtomicIntegerFieldUpdater {
//創(chuàng)建一個(gè)age的更新器
private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
public static void main(String[] args) {
User user = new User("小明", 17);
int oldValue = updater.getAndAdd(user, 1);
System.out.println(oldValue);//17
System.out.println(updater.get(user));//18
}
static class User {
private String userName;
public volatile int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
通過(guò)AtomicIntegerFieldUpdater.newUpdater(User.class,"age")創(chuàng)建一個(gè)age的更新器,然后調(diào)用getAndAdd(user, 1)進(jìn)行年齡加1操作,從17歲變?yōu)?8歲。
小編推薦閱讀機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)
閱讀華為Mate品牌盛典:HarmonyOS NEXT加持下游戲性能得到充分釋放
閱讀實(shí)現(xiàn)對(duì)象集合與DataTable的相互轉(zhuǎn)換
閱讀鴻蒙NEXT元服務(wù):論如何免費(fèi)快速上架作品
閱讀算法與數(shù)據(jù)結(jié)構(gòu) 1 - 模擬
閱讀升訊威在線客服與營(yíng)銷(xiāo)系統(tǒng)介紹
閱讀基于鴻蒙NEXT的血型遺傳計(jì)算器開(kāi)發(fā)案例
閱讀5. Spring Cloud OpenFeign 聲明式 WebService 客戶端的超詳細(xì)使用
閱讀Java代理模式:靜態(tài)代理和動(dòng)態(tài)代理的對(duì)比分析
閱讀Win11筆記本“自動(dòng)管理應(yīng)用的顏色”顯示規(guī)則
閱讀本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請(qǐng)發(fā)郵件[email protected]
湘ICP備2022002427號(hào)-10 湘公網(wǎng)安備:43070202000427號(hào)© 2013~2025 haote.com 好特網(wǎng)