Java的原子類無鎖并發(fā)利器詳解
前言引入
當(dāng)存在如下場景,兩個線程同時去將count值累加一萬次,那么如下代碼是否存在線程安全問題呢?
public class Test2 {
public static void main(String[] args) throws InterruptedException {
TestCount testCount = new TestCount();
Thread T1 = new Thread(()->{
testCount.add10k();
});
Thread T2 = new Thread(()->{
testCount.add10k();
});
T1.start();
T2.start();
T1.join();
T2.join();
System.out.println(testCount.count);
}
}
class TestCount{
long count = 0;
void add10k(){
int idx = 0;
while (idx++ < 10000){
count += 1;
}
}
}顯然是存在的,最終的結(jié)果顯然是小于兩萬的,原因是count值存在可見性問題,count+=1也存在原子性的問題。
一般思路都是將count采用volatile修飾,count+=1原子性問題采用synchronized互斥鎖解決。
class TestCount{
volatile long count = 0;
synchronized void add10k(){
int idx = 0;
while (idx++ < 10000){
count += 1;
}
}
}但是對于這種簡單的互斥操作,需要采用synchronized這種比較重的互斥鎖嗎?有沒有更優(yōu)的解決辦法呢?當(dāng)然存在,采用原子類無鎖方案能夠極大的提升性能
class TestCount{
AtomicLong count = new AtomicLong(0);
void add10k(){
int idx = 0;
while (idx++ < 10000){
count.getAndIncrement();
}
}
}原子類同樣能夠解決互斥性問題、原子性問題除此之外,因為原子類是無鎖操作,沒有用互斥鎖解決帶來的加鎖解決性能消耗,這種絕佳方案是怎么做到的呢?
無鎖方案實現(xiàn)原理
無鎖方案之所以能夠保證原子性,主要還是硬件保證,CPU為了解決并發(fā)問題,提供了CAS(Compare And Swap)指令即比較并交換,CAS一般包含三個參數(shù),共享變量的內(nèi)存地址A,用于比較的期望值B,更新共享變量C,當(dāng)共享變量的內(nèi)存地址A的值和共享變量B的值相等時,才將共享變量的內(nèi)存地址A處的值更新為共享變量C。
將場景語義化如下
class SimpleCAS{
int count;
public synchronized int cas(int expect,int newCount){
// 讀取count值
int oldcount = count;
// 讀取的count值和期望值比較
if (oldcount == expect){
count = newCount;
}
// 返回老值
return oldcount;
}
}CAS指令判斷并不是一次性的,如果比較失敗又會重新取最新的值和期望值判斷直到成功。
class SimpleCAS{
volatile int count;
public synchronized int cas(int expect,int newCount){
// 讀取count值
int oldcount = count;
// 讀取的count值和期望值比較
if (oldcount == expect){
count = newCount;
}
// 返回老值
return oldcount;
}
// 自旋操作,執(zhí)行cas方法
public void addOne(){
int newCount = 0;
do {
newCount = count + 1;
}while (count != cas(count,newCount));
}
}ABA問題
原子類雖然好用,但是一定需要的坑就是ABA問題,假如存在共享變量A值為5,線程T1將共享變量A的值改為2,而線程T2將共享變量A改為3,線程T3又將共享變量A改為2,那么對于線程T1來講共享變量A是沒有變的嗎?顯然不是,可能大多數(shù)場景我們并不關(guān)心ABA問題,對于基礎(chǔ)數(shù)據(jù)遞增可能認(rèn)為值不變就夠了,并不關(guān)心值是否已經(jīng)修改,但是對于引用類型呢,這就一定要注意ABA問題,兩個A雖然相等,但是屬性可能已經(jīng)發(fā)生變化。
原子類提供工具類解決ABA問題AtomicStampedReference和AtomicMarkableReference
public static void main(String[] args) throws InterruptedException {
// 初始化原子類 定義初始引用和標(biāo)識
// AtomicMarkableReference同理可得,只是將版本戳換成了boolean類型
AtomicStampedReference<String> reference = new AtomicStampedReference<>("zhangsan",1001);
/**
* expectedReference 期望的引用
* newReference 新的引用
* expectedStamp 期望的版本戳
* newStamp 新的版本戳
* 只有當(dāng)期望引用和期望版本戳都符合實際版本戳和引用才能替換成功
*/
reference.compareAndSet("zhangsan","lisi",1002,1003);
// zhangsan 替換失敗的原因是期望版本戳和實際版本戳不匹配
System.out.println(reference.getReference());
reference.compareAndSet("zhangsan","lisi",1001,1002);
// lisi 替換成功
System.out.println(reference.getReference());
reference.compareAndSet("lisi1","wangwu",1002,1003);
// lisi 替換失敗的原因是期望引用和實際引用不匹配
System.out.println(reference.getReference());
}getAndIncrement源碼分析
/**
* this 指當(dāng)前對象
* valueOffset 指內(nèi)存地址偏移量
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
* this 指當(dāng)前對象
* valueOffset 指內(nèi)存地址偏移量
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 就是讀取主內(nèi)存的值
var5 = this.getIntVolatile(var1, var2);
// this.compareAndSwapInt方法就是對應(yīng)上訴的cas方法,不過返回值是boolean類型
// var5讀取的主內(nèi)存值
// 更新成功返回true,跳出循環(huán)
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}原子工具類總覽
原子工具類是一個大家族,根據(jù)使用可以分為原子化基本數(shù)據(jù)類型、原子化的對象引用類型、原子化數(shù)組、原子化對象屬性更新器和原子化的累加器,方法都基本類似不需要刻意去記,需要用到的時候再來查就可以,但是需要有個印象,如下圖所示。

到此這篇關(guān)于Java的原子類無鎖并發(fā)利器詳解的文章就介紹到這了,更多相關(guān)原子類無鎖并發(fā)利器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud配置客戶端ConfigClient接入服務(wù)端
這篇文章主要為大家介紹了SpringCloud配置客戶端ConfigClient接入服務(wù)端,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Spring?Security中使用authorizeRequests遇到的問題小結(jié)
Spring?是非常流行和成功的?Java?應(yīng)用開發(fā)框架,Spring?Security?正是?Spring?家族中的成員,這篇文章主要介紹了Spring?Security中使用authorizeRequests遇到的問題,需要的朋友可以參考下2023-02-02
Java之Error與Exception的區(qū)別案例詳解
這篇文章主要介紹了Java之Error與Exception的區(qū)別案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
SpringBoot?AOP?Redis實現(xiàn)延時雙刪功能實戰(zhàn)
本文主要介紹了SpringBoot?AOP?Redis實現(xiàn)延時雙刪功能實戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08

