貍貓換太子
#在開(kāi)始問(wèn)題的闡述之前,我們先來(lái)看看一則小故事:
北宋宋真宗皇后死後,當(dāng)時(shí)他的兩位愛(ài)妃劉妃和李妃都懷了孕,很顯然,誰(shuí)生了兒子,誰(shuí)就有可能立為正宮。劉妃久懷嫉妒之心,唯恐李妃生了兒子被立為皇后,於是與宮中總管都堂郭槐定計(jì),在接生婆尤氏的配合下,乘李妃分娩時(shí)由於血暈而人事不知之機(jī),將一貍貓剝?nèi)テっ?,血淋淋,光油油地?fù)Q走了剛出世的太子。劉妃命宮女寇珠勒死太子,寇珠於心不忍,暗中將太子交付宦官陳琳,陳琳將太子裝在提盒中送至八賢王處撫養(yǎng)。再說(shuō)真宗看到被剝了皮的貍貓,以為李妃產(chǎn)下了一個(gè)妖物,乃將其貶入冷宮。不久,劉妃臨產(chǎn),生了個(gè)兒子,被立為太子,劉妃也被冊(cè)立為皇后。誰(shuí)知六年後,劉後之子病夭。真宗再無(wú)子嗣,就將其皇兄八賢王之子(實(shí)為當(dāng)年被換走的皇子)收為義子,並立為太子。
從這故事看出來(lái),太子出生被換成了貍貓,最後陰差陽(yáng)錯(cuò)又回歸成為太子。雖然結(jié)果是一樣的,但是過(guò)程曲折了,太子真的是命途多舛啊。
為什麼要說(shuō)這個(gè)故事?其實(shí)跟我們今天要介紹的問(wèn)題有很大的關(guān)係。同樣的結(jié)果,可能中間不知道發(fā)生了多少次操作,那我們能認(rèn)為他沒(méi)改變嗎?在不同的業(yè)務(wù)場(chǎng)景下,我們要仔細(xì)考慮這個(gè)問(wèn)題。
ABA問(wèn)題描述
#在多執(zhí)行緒場(chǎng)景下CAS
會(huì)出現(xiàn) ABA
問(wèn)題,關(guān)於ABA問(wèn)題這裡簡(jiǎn)單科普下,例如有2個(gè)線程同時(shí)對(duì)同一個(gè)值(初始值為A)進(jìn)行CAS操作,這三個(gè)線程如下:
執(zhí)行緒1,期望值為A,欲更新的值為B #執(zhí)行緒2,期望值為A,欲更新的值為B
線程1搶先獲得CPU時(shí)間片,而線程2因?yàn)槠渌蜃枞耍€程1取值與期望的A值比較,發(fā)現(xiàn)相等然後將值更新為B,然後這個(gè)時(shí)候出現(xiàn)了線程3,期望值為B,欲更新的值為A,線程3取值與期望的值B比較,發(fā)現(xiàn)相等則將值更新為A,此時(shí)線程2從阻塞中恢復(fù),並且獲得了CPU時(shí)間片,這時(shí)候線程2取值與期望的值A(chǔ)比較,發(fā)現(xiàn)相等則將值更新為B,雖然線程2也完成了操作,但是線程2並不知道值已經(jīng)經(jīng)過(guò)了A->B->A的變化過(guò)程。
舉個(gè)具體的例子說(shuō)明
#小明在提款機(jī),提取了50元,因?yàn)樘峥顧C(jī)問(wèn)題,有兩個(gè)線程,同時(shí)把餘額從100變成50:
-
#線程1(提款機(jī)):取得目前值100,期望更新為50; 執(zhí)行緒2(提款機(jī)):取得目前值100,期望更新為50; 執(zhí)行緒1成功執(zhí)行,執(zhí)行緒2某種原因block了; 這時(shí),某人給小明匯款50; 線程3(預(yù)設(shè)):取得目前值50,期望更新為100,這時(shí)候線程3成功執(zhí)行,餘額變成100; #線程2從Block恢復(fù),取得到的也是100,compare之後,繼續(xù)更新餘額為50。
此時(shí)可以看到,實(shí)際餘額應(yīng)該是100(100-50 50)
,但實(shí)際上變成了50(100-50 50 -50)
這就是ABA問(wèn)題帶來(lái)錯(cuò)誤提交結(jié)果。
解決方法
#要解決ABA問(wèn)題,可以增加一個(gè)版本號(hào),當(dāng)記憶體位置V的值每次被修改後,版本號(hào)碼都加1
程式碼範(fàn)例
#透過(guò)AtomicStampedReference來(lái)解決ABA問(wèn)題
AtomicStampedReference內(nèi)部維護(hù)了物件值和版本號(hào),在建立AtomicStampedReference物件時(shí),需要傳入初始值和初始版本號(hào);
#當(dāng)AtomicStampedReference設(shè)定物件值時(shí),物件值以及狀態(tài)戳記都必須滿足期望值,寫入才會(huì)成功。
3、執(zhí)行緒t1完成ABA操作,版本號(hào)遞增到3
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100,1); public static void main(String[] args) { //第一個(gè)線程 new Thread(() -> { System.out.println("t1拿到的初始版本號(hào):" + atomicStampedReference.getStamp()); //睡眠1秒,是為了讓t2線程也拿到同樣的初始版本號(hào) try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100, 101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); atomicStampedReference.compareAndSet(101, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); },"t1").start(); // 第二個(gè)線程 new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println("t2拿到的初始版本號(hào):" + stamp); //睡眠3秒,是為了讓t1線程完成ABA操作 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("最新版本號(hào):" + atomicStampedReference.getStamp()); System.out.println(atomicStampedReference.compareAndSet(100, 2019,stamp,atomicStampedReference.getStamp() + 1) + "\t當(dāng)前值:" + atomicStampedReference.getReference()); },"t2").start(); }
1、初始值100,初始版本號(hào)1 2、執(zhí)行緒t1和t2拿到一樣的初始版本號(hào)
4、執(zhí)行緒t2完成CAS操作,最新版本號(hào)碼已經(jīng)變成3,跟線程t2之前拿到的版本號(hào)1不相等,操作失敗
###執(zhí)行結(jié)果:######t1拿到的初始版本號(hào):1 t2拿到的初始版本號(hào):1 最新版本號(hào):3 false 當(dāng)前值:100################透過(guò)AtomicMarkableReference解決ABA問(wèn)題############# ##AtomicStampedReference###可以為引用加上版本號(hào),追蹤引用的整個(gè)變化過(guò)程,如:A -> B -> C -> D -> A,透過(guò)AtomicStampedReference,我們可以知道,引用變量中途被更改了3次。但是,有時(shí)候,我們並不關(guān)心引用變數(shù)更改了幾次,只是單純的關(guān)心是否更改過(guò),所以就有了###AtomicMarkableReference###, AtomicMarkableReference的唯一差異就是不再用int識(shí)別來(lái)引用,而是使用boolean變數(shù)-表示引用變數(shù)是否被更改過(guò)。 ###
private static AtomicMarkableReference<Integer> atomicMarkableReference = new AtomicMarkableReference<Integer>(100,false); public static void main(String[] args) { // 第一個(gè)線程 new Thread(() -> { System.out.println("t1版本號(hào)是否被更改:" + atomicMarkableReference.isMarked()); //睡眠1秒,是為了讓t2線程也拿到同樣的初始版本號(hào) try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicMarkableReference.compareAndSet(100, 101,atomicMarkableReference.isMarked(),true); atomicMarkableReference.compareAndSet(101, 100,atomicMarkableReference.isMarked(),true); },"t1").start(); // 第二個(gè)線程 new Thread(() -> { boolean isMarked = atomicMarkableReference.isMarked(); System.out.println("t2版本號(hào)是否被更改:" + isMarked); //睡眠3秒,是為了讓t1線程完成ABA操作 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("是否更改過(guò):" + atomicMarkableReference.isMarked()); System.out.println(atomicMarkableReference.compareAndSet(100, 2019,isMarked,true) + "\t當(dāng)前值:" + atomicMarkableReference.getReference()); },"t2").start(); }######1、初始值100,初始版本號(hào)未修改false ###2、線程t1和t2拿到一樣的初始版本號(hào)都沒(méi)有被修改false ###3、線程t1完成ABA操作,版本號(hào)碼被修改true ###4、線程t2完成CAS操作,版本號(hào)碼已經(jīng)變成true,跟線程t2之前拿到的版本號(hào)false不相等,操作失敗####### #####執(zhí)行結(jié)果:######
t1版本號(hào)是否被更改:false t2版本號(hào)是否被更改:false 是否更改過(guò):true false 當(dāng)前值:100
多說(shuō)幾句
以上是本期關(guān)于CAS領(lǐng)域的一個(gè)經(jīng)典ABA問(wèn)題的解析,不知道你在實(shí)際的工作中有沒(méi)有遇到過(guò),但是在面試中這塊是并發(fā)知識(shí)考查的重點(diǎn)。如果你還沒(méi)接觸過(guò)此類的問(wèn)題,我的建議是你自己將上面的代碼運(yùn)行一下,結(jié)合理論去理解一下ABA問(wèn)題所帶來(lái)的問(wèn)題以及如何解決他,這對(duì)你日后的開(kāi)發(fā)工作也是有莫大的幫助的!
以上是面試官問(wèn)你:你知道什麼是ABA問(wèn)題嗎?的詳細(xì)內(nèi)容。更多資訊請(qǐng)關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

熱AI工具

Undress AI Tool
免費(fèi)脫衣圖片

Undresser.AI Undress
人工智慧驅(qū)動(dòng)的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強(qiáng)大的PHP整合開(kāi)發(fā)環(huán)境

Dreamweaver CS6
視覺(jué)化網(wǎng)頁(yè)開(kāi)發(fā)工具

SublimeText3 Mac版
神級(jí)程式碼編輯軟體(SublimeText3)

透過(guò)CAS(CentralAuthenticationService)實(shí)現(xiàn)PHP安全驗(yàn)證隨著網(wǎng)際網(wǎng)路的快速發(fā)展,使用者權(quán)限管理和身分驗(yàn)證越來(lái)越重要。在開(kāi)發(fā)WEB應(yīng)用程式時(shí),保護(hù)使用者資料和防止未經(jīng)授權(quán)存取是至關(guān)重要的。為了實(shí)現(xiàn)這一目標(biāo),我們可以使用CAS(CentralAuthenticationService)來(lái)進(jìn)行PHP的安全驗(yàn)證。 CAS

1.說(shuō)明當(dāng)多個(gè)線程同時(shí)對(duì)某個(gè)資源進(jìn)行CAS操作時(shí),只有一個(gè)線程成功,但不會(huì)堵塞其他線程,其他線程只會(huì)收到操作失敗的訊號(hào)。可見(jiàn)CAS其實(shí)是樂(lè)觀的鎖。 2.實(shí)例跟隨AtomInteger的程式碼,我們可以發(fā)現(xiàn)最終呼叫的是sum.misc.Unsafe。看看Unsafe這個(gè)名字,它是一個(gè)不安全的類別,它利用了Java類別和可見(jiàn)性規(guī)則中恰到好處的漏洞。為了速度,Unsafe在Java的安全標(biāo)準(zhǔn)上做出了一些妥協(xié)。 publicfinalnativebooleancompareAndSwapInt(Objec

CAS解釋:CAS(compareandswap),比較並交換。可以解決多執(zhí)行緒並行情況下使用鎖造成效能損耗的一種機(jī)制。CAS運(yùn)算包含三個(gè)運(yùn)算元—記憶體位置(V)、預(yù)期原值(A)和新值(B)。如果記憶體位置的值與預(yù)期原值相匹配,那麼處理器會(huì)自動(dòng)將該位置值更新為新值。否則,處理器不做任何操作。一個(gè)執(zhí)行緒從主記憶體得到num值,並對(duì)num進(jìn)行操作,寫入值的時(shí)候,執(zhí)行緒會(huì)把第一次取到的num值和主記憶體中num值進(jìn)行比較,如果相等,就會(huì)改變後的num寫入主內(nèi)存,如果不相等,則一直循環(huán)對(duì)比,知道成功為止。 CAS產(chǎn)

什麼是CASCAS是CompareAndSwap,即比較和交換。為什麼CAS沒(méi)有用到鎖還能保證並發(fā)情況下安全的操作數(shù)據(jù)呢,名字其實(shí)非常直觀的表明了CAS的原理,具體修改數(shù)據(jù)過(guò)程如下:用CAS操作數(shù)據(jù)時(shí),將數(shù)據(jù)原始值和要修改的值一併傳遞給方法比較當(dāng)前目標(biāo)變數(shù)值與傳進(jìn)去的原始值是否相同如果相同,表示目標(biāo)變數(shù)沒(méi)有被其他執(zhí)行緒修改,直接修改目標(biāo)變數(shù)值即可如果目標(biāo)變數(shù)值與原始值不同,那麼證明目標(biāo)變數(shù)已經(jīng)被其他線程修改過(guò),本次CAS修改失敗從上述過(guò)程可以看到CAS其實(shí)保證的是安全的修改數(shù)據(jù),但是修改存在失敗的

有鎖並發(fā)對(duì)於大多數(shù)程式設(shè)計(jì)師(當(dāng)然我也基本上是其中一員),並發(fā)程式幾乎就等價(jià)於為相關(guān)資料結(jié)構(gòu)加上一個(gè)鎖(Mutex)。例如如果我們需要一個(gè)支援並發(fā)的棧,那最簡(jiǎn)單的方法就是給一個(gè)單執(zhí)行緒的棧加上鎖定std::sync::Mutex。 (加上Arc是為了能讓多個(gè)執(zhí)行緒都擁有堆疊的所有權(quán))usestd::sync::{Mutex,Arc};#[derive(Clone)]structConcurrentStack{inner:Arc,}implConcurrentStack{pubfnnew()-> Self{

程式中,我創(chuàng)建了100個(gè)線程,每個(gè)線程中對(duì)共享變數(shù)inc進(jìn)行累加10000次的操作,如果是同步執(zhí)行的話,inc最終的值應(yīng)該是1000000,但我們知道在多線程中,程式是並發(fā)執(zhí)行的,也就是說(shuō)不同的執(zhí)行緒可能會(huì)同時(shí)讀取到主記憶體相同的值

本期關(guān)於CAS領(lǐng)域的一個(gè)經(jīng)典ABA問(wèn)題的解析,不知道你在實(shí)際的工作中有沒(méi)有遇到過(guò),但是在面試中這塊是並發(fā)知識(shí)考查的重點(diǎn)。如果你還沒(méi)接觸過(guò)這類的問(wèn)題,我的建議是你自己將上面的程式碼執(zhí)行一下

1.新建springboot專案並引入依賴org.jasig.cas.clientcas-client-support-springboot3.6.22.設(shè)定@EnableCasClient註解packagecom.codetiler.demo;importorg.jasig.cas.client.boot.configuration.EnableCasClientimportorg.jasig.cas.client.boot.configuration.EnableCasClientimportorg. springframework.boot.SpringApplication;importorg.spring
