什么是CAS
所謂的CAS既是compareAndSwap的縮寫恋技,翻譯過來既是“對比和交換”的意思雄人。
那怎么去對比呢妈倔,畫個小圖理解下:
3個值分別對應(yīng)的含義:
新的值:需要修改過后的值
內(nèi)存變量的值:在內(nèi)存的值是多少
-
舊的預(yù)期值:這里相當(dāng)于內(nèi)存的值的一個副本,copy過來的
如果內(nèi)存的值等于舊的預(yù)期值里烦,那么就把新的值替換為內(nèi)存中的值损离,否則什么都不做
這里就是自旋鎖
AtomicInteger就是用到CAS來實(shí)現(xiàn)的哥艇。 用代碼理解下:
// 類似上述全局內(nèi)存共享變量V
AtomicInteger atomicInteger = new AtomicInteger(0);
// CAS操作
boolean b = atomicInteger.compareAndSet(0, 1);
System.out.println("是否修改成功:" + b);
System.out.println("修改后的值:"+ atomicInteger.get());
打印:
是否修改成功:true 修改后的值:1
那么此時(shí)有多個線程同時(shí)請求如下:
線程T1草冈,T2同時(shí)需要修改內(nèi)存中的變量值“加1”操作流程如下
此時(shí)線程T1她奥,T2內(nèi)存中的值都為0
T1:此時(shí)V=0,E=0,發(fā)現(xiàn)V=E怎棱,那么把V修改為N哩俭,那么現(xiàn)在V=1
T2:此時(shí)V=1,E=0,發(fā)下V!=E拳恋,那么重新讀取(自旋)內(nèi)存中的值把E的值改成1凡资,這時(shí)V=E了。
ABA問題
什么是ABA問題。 假設(shè)變量0被線程B修改成了2隙赁,再被自己或者其他線程又修改成了0垦藏,那么對于線程A來說,并沒有感知到伞访。因?yàn)榫€程A對比了0和0還是相等的掂骏。這就是ABA問題。那么如何解決呢厚掷?
加版本號處理弟灼。怎么處理?
也就是不管哪個線程修改了變量值冒黑,都把版本號加一田绑。那么其他線程讀取的時(shí)候就感知到了。原來被修改過
底層其實(shí)是通過C去實(shí)現(xiàn)的
C語言指令:
lock cmpxchg
什么是AQS
所謂的AQS既是AbstractQueuedSynchronizer的縮寫
AbstractQueuedSynchronizer(AQS)是JDK中實(shí)現(xiàn)并發(fā)編程的核心抡爹,平時(shí)我們工作中經(jīng)常用到的ReentrantLock掩驱,CountDownLatch等都是基于它來實(shí)現(xiàn)的。
以ReentrantLock為例:
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
底層源碼其實(shí)是通過Sync這個來實(shí)現(xiàn)的:
public void lock() {
sync.lock();
}
再點(diǎn)進(jìn)去發(fā)現(xiàn)冬竟,其實(shí)就是AbstractQueuedSynchronizer的子類
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
...
}
此時(shí)發(fā)現(xiàn)lock抽象方法的實(shí)現(xiàn)有兩個
分別是FairSync(公平鎖)和NonfairSync(非公平鎖)均屬于ReentrantLock內(nèi)部類
那么怎么決定使用公平鎖或非公平鎖呢,可以通過ReentrantLock的構(gòu)造函數(shù)欧穴,默認(rèn)是用非公平鎖。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平鎖和非公平鎖
公平鎖:所謂的公平鎖呢會按照嚴(yán)格的執(zhí)行順序來分配的泵殴。
非公平鎖:競爭鎖的線程允許來插隊(duì)來搶占鎖的資源苔可。
畫個小圖理解下:
公平鎖
此時(shí)有4個線程分別是T1,T2,T3,T4,同時(shí)調(diào)用這個方法。那么必然只會有一個線程會拿到鎖袋狞。
假設(shè)此時(shí)T1線程拿到了鎖,那么其他T2,T3,T4會阻塞映屋。那么作為公平鎖怎么處理呢苟鸯,讓其實(shí)現(xiàn)一個公平狀態(tài)
可以使其T2,T3,T4放入一個鏈表中等待,那么T1釋放的鎖棚点,這個時(shí)候呢T1不會喚醒T2,T3,T4早处,只會喚醒(LockSupport.unpark)T2,交給T2持有鎖瘫析,
那么T1呢砌梆,加入鏈表后續(xù)等待。就變成了這樣贬循。
那什么是非公平的呢咸包?非公平其實(shí)就是通過cpu去爭搶,在多線程的場景下如果哪個線程搶到了鎖杖虾,那么就會持有這把鎖烂瘫。
有的時(shí)候就會發(fā)現(xiàn)多線程的場景下會有相同的線程去執(zhí)行。而并非上述的T1,T2,T3,T4按照順序去執(zhí)行奇适。
非公平鎖:
首先T1,T2,T3,來CAS搶占鎖坟比,假設(shè)此時(shí)T1搶占成功了芦鳍,那么這時(shí)T2,T3會阻塞,怎么阻塞呢葛账,他們會加入到一個鏈表中等待柠衅,
注意:這是一個雙向鏈表,且有頭有尾籍琳。首節(jié)點(diǎn)是獲取到鎖的節(jié)點(diǎn)菲宴,其他的都是阻塞的節(jié)點(diǎn)
這時(shí)我們在總結(jié)寫公平鎖與非公平鎖的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):所有的線程都能得到資源,不會餓死在隊(duì)列中巩割。
缺點(diǎn):吞吐量會下降很多裙顽,隊(duì)列里面除了第一個線程,其他的線程都會阻塞宣谈,cpu喚醒阻塞線程的開銷會很大愈犹。
非公平鎖:多個線程去獲取鎖的時(shí)候,會直接去嘗試獲取闻丑,獲取不到漩怎,再去進(jìn)入等待隊(duì)列,如果能獲取到嗦嗡,就直接獲取到鎖勋锤。
優(yōu)點(diǎn):可以減少CPU喚醒線程的開銷,整體的吞吐效率會高點(diǎn)侥祭,CPU也不必取喚醒所有線程叁执,會減少喚起線程的數(shù)量。
缺點(diǎn):你們可能也發(fā)現(xiàn)了矮冬,這樣可能導(dǎo)致隊(duì)列中間的線程一直獲取不到鎖或者長時(shí)間獲取不到鎖谈宛,導(dǎo)致餓死。
非公平鎖的設(shè)計(jì)理念
其實(shí)像ReentrantLock還是像synchronized等都是一樣胎署,默認(rèn)都是非公平鎖的策略吆录,之所以這么設(shè)計(jì),是考慮到性能這方面的原因琼牧,
因?yàn)槿绻凑展芥i的策略去進(jìn)行阻塞等待恢筝,同時(shí)AQS又喚醒正在等待的線程,這里會涉及到一個內(nèi)核態(tài)的切換巨坊,對性能會有一定的影響撬槽。如果是非公平鎖呢,當(dāng)前線程正好在上一個線程釋放的臨界點(diǎn)去搶占到了鎖這意味著這個線程不會進(jìn)行內(nèi)核態(tài)的切換
AQS雙向鏈表的作用?
1.線程中斷 需要刪除當(dāng)前線程被封裝的Node節(jié)點(diǎn) 這是就變成了鏈表的刪除操作
如果想從CLH 單向鏈表中間刪除一個 Node趾撵,因?yàn)橹痪S護(hù)了前一個節(jié)點(diǎn)的指針恢氯,想要知道后一個節(jié)點(diǎn)的指針的話,不通過從tail開始使用快慢指針遍歷是無法辦到的。
因此直接維護(hù)prev勋拟、next指針勋磕,以降低刪除操作的復(fù)雜性。 說白了就是降低刪除的復(fù)雜度
2.喚醒后續(xù)線程 而當(dāng)多線程競爭時(shí)敢靡,CLH的輪詢是非常耗費(fèi)性能的挂滓,無論是對CPU還是總線來說,都是一種巨大的壓力啸胧。
所以在CLH的prev基礎(chǔ)上增加了next