互聯(lián)網(wǎng)行業(yè)分為哪幾類排名優(yōu)化方法
常見的鎖策略
此處談到的鎖策略,不局限于 Java,C++,Python,數(shù)據(jù)庫,操作系統(tǒng)……但凡是涉及到鎖,都是可以應(yīng)用到下列的鎖策略的
樂觀鎖 vs 悲觀鎖
鎖的實(shí)現(xiàn)者,預(yù)測接下來鎖沖突(鎖競爭,兩個(gè)線程針對一個(gè)對象加鎖,產(chǎn)生阻塞等待了)的概率是大,還是不大,根據(jù)這個(gè)沖突的概率,來接下來做什么
~~ 這不是兩把具體的鎖,而是“兩類鎖”.
悲觀鎖:預(yù)測鎖競爭不是很激烈.
樂觀鎖:預(yù)測鎖競爭會(huì)很激烈.
通常來說,悲觀鎖一般做的工作更多一些,效率更低一些,樂觀鎖做的工作會(huì)更少一些,效率更高一點(diǎn).但是并不絕對.
悲觀鎖和樂觀鎖,唯一的區(qū)別,主要就是看預(yù)測鎖競爭激烈程度的結(jié)論.
輕量級鎖 vs 重量級鎖
輕量級鎖: 加鎖解鎖,過程更快更高效.
重量級鎖: 加鎖解鎖,過程更慢,更低效.
一個(gè)樂觀鎖很可能也是一個(gè)輕量級鎖,一個(gè)悲觀鎖很可能也是一個(gè)重量級鎖.
多數(shù)情況下,樂觀鎖,也是一個(gè)輕量級鎖.
多數(shù)情況下,悲觀鎖也是一個(gè)重量級鎖.
自旋鎖 vs 掛起等待鎖
自旋鎖是輕量級鎖的一種典型實(shí)現(xiàn).
掛起等待鎖是重量級鎖的一種典型實(shí)現(xiàn).
互斥鎖 vs 讀寫鎖
互斥鎖
synchronized,是互斥鎖
synchronized 只有兩個(gè)操作:
1.進(jìn)入代碼塊,加鎖
2.出了代碼塊,解鎖
加鎖,就只是單純的加鎖,沒有更細(xì)化的區(qū)分了
讀寫鎖
~~ 讀寫鎖,能夠把讀和寫兩種加鎖區(qū)分開
讀寫鎖:
1.給讀加鎖
2.給寫加鎖
3.解鎖
注: 如果多個(gè)線程讀同一個(gè)變量,不會(huì)涉及到線程安全問題!!!
讀寫鎖中,約定:
1.讀鎖和讀鎖之間,不會(huì)鎖競爭.不會(huì)產(chǎn)生阻塞等待,不會(huì)影響程序的速度,代碼執(zhí)行很快.
2.寫鎖和寫鎖之間,有鎖競爭,減慢速度,但是保證準(zhǔn)確性.
3.讀鎖和寫鎖之間,也有鎖競爭,減慢速度,但是保證準(zhǔn)確性.
注:
1.非必要不加鎖.
2.讀寫鎖更適合于,一寫多讀的情況.
3.多線程針對同一個(gè)變量并發(fā)讀,這時(shí)是沒有線程安全問題的,也就不需要加鎖控制.
4.很多開發(fā)場景中,讀操作非常高頻,比寫操作的頻率高很多.
5.在Java標(biāo)準(zhǔn)庫里面也提供了讀寫鎖的具體實(shí)現(xiàn)(兩個(gè)類,讀鎖類,寫鎖類).
公平鎖 vs 非公平鎖
此處把公平定義成“先來后到”
公平鎖: 當(dāng)女神分手之后,就由等待隊(duì)列中,最早來的舔狗上位.
非公平鎖: 雨露均沾了.
注: 操作系統(tǒng)和 Java synchronized 原生都是“非公平鎖”,操作系統(tǒng)這里的針對加鎖的控制,本身就依賴于線程調(diào)度順序.這個(gè)調(diào)度順序是隨機(jī)的,不會(huì)考慮到這個(gè)線程等待鎖多久了.
要想實(shí)現(xiàn)公平鎖,就得在這個(gè)基礎(chǔ)上,能夠引入額外的東西(引入一個(gè)隊(duì)列,讓這些加鎖的線程去排隊(duì)).
可重入鎖 vs 不可重入鎖
不可重入鎖: 一個(gè)線程針對一把鎖,連續(xù)加鎖兩次,出現(xiàn)死鎖.
可重入鎖: 一個(gè)線程針對一把鎖,連續(xù)加鎖多次都不會(huì)死鎖.
注: 系統(tǒng)原生的鎖,C++標(biāo)準(zhǔn)庫的鎖,Python標(biāo)準(zhǔn)庫的鎖…都不是可重入的鎖!
synchronized是個(gè)"可重入鎖",(加鎖的時(shí)候會(huì)判定一下,看當(dāng)前嘗試申請鎖的線程是不是已經(jīng)就是鎖的擁有者了,如果是,直接放行)
synchronized鎖
針對上述六組鎖策略, synchronized這把鎖屬于哪種呢??
synchronized 既是悲觀鎖,也是樂觀鎖 ~~ synchronized會(huì)根據(jù)當(dāng)前鎖競爭的激烈程度,自適應(yīng).
既是輕量級鎖,也是重量級鎖 ~~ synchronized默認(rèn)是輕量級鎖,如果發(fā)現(xiàn)當(dāng)前鎖競爭比較激烈,就會(huì)轉(zhuǎn)換成重量級鎖.
synchronized這里的輕量級鎖部分基于自旋鎖的方式實(shí)現(xiàn),synchronized這里的重量級鎖部分基于掛起等待鎖的方式實(shí)現(xiàn).
synchronized不是讀寫鎖.
synchronized是非公平鎖.
synchronized是可重入鎖.
總結(jié): 上述談到的六種鎖策略,可以視為是“鎖的形容詞”.
CAS
CAS ~~ 全稱Compare and swap, 字面意思:”比較并交換“
一個(gè) CAS 涉及到以下操作 :
我們假設(shè)內(nèi)存中的原數(shù)據(jù)V,舊的預(yù)期值A(chǔ),需要修改的新值B。
1.比較 A 與 V 是否相等。(比較)
2.如果比較相等,將 B 寫入 V。(交換)
3.返回操作是否成功
此處最特別的地方,上述這個(gè) CAS 的過程,并非是通過一段代碼實(shí)現(xiàn)的,而是通過一條 CPU 指令完成的 => CAS 操作是原子的 ~~ 就可以在一定程度上回避線程安全問題
因此解決線程安全問題除了加鎖之外,又有了一個(gè)新的方向了.
小結(jié): CAS 可以理解成是 CPU 給我們提供的一個(gè)特殊指令,通過這個(gè)指令,就可以一定程度的處理線程安全問題.
CAS 偽代碼
注: 下面寫的代碼不是原子的, 真實(shí)的 CAS 是一個(gè)原子的硬件指令完成的. 這個(gè)偽代碼只是輔助理解 CAS 的工作流程.
CAS 的應(yīng)用場景
1.實(shí)現(xiàn)原子類(Java 標(biāo)準(zhǔn)庫里提供的類)
標(biāo)準(zhǔn)庫中提供了
java.util.concurrent.atomic
包, 里面的類都是基于這種方式來實(shí)現(xiàn)的.典型的就是AtomicInteger
類,其中的getAndIncrement
相當(dāng)于i++
操作.
import java.util.concurrent.atomic.AtomicInteger;/*** Created with IntelliJ IDEA.* Description:* User: fly(逐夢者)* Date: 2023-10-09* Time: 10:49*/
public class ThreadDemo28 {public static void main(String[] args) throws InterruptedException {// 這些原子類,就是基于 CAS 實(shí)現(xiàn)了 自增,自減等操作.此時(shí)進(jìn)行這類操作不需要加鎖,也是線程安全的.AtomicInteger count = new AtomicInteger(0);// 使用原子類, 來解決線程安全問題Thread t1 = new Thread(() -> {for (int i = 0; i < 5_0000; i++) {// 因?yàn)?java 不支持運(yùn)算符重載,所以只能使用普通方法來表示自增自減count.getAndIncrement();// count++//count.incrementAndGet(); => ++ count//count.getAndDecrement(); => count--//count.decrementAndGet(); => -- count}});Thread t2 = new Thread(() -> {for (int i = 0; i < 5_0000; i++) {count.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count.get());}
}
偽代碼實(shí)現(xiàn)AtomicInteger
2.實(shí)現(xiàn)自旋鎖
自旋鎖偽代碼
注: CAS 屬于“特殊方法”,synchronized 屬于“通用方法” –> 各種場景,都能使用(打擊面廣)
CAS 的典型問題: ABA 問題
CAS 在運(yùn)行中的核心,檢查 value 和 oldValue 是否一致.如果一致,就視為value 中途沒有被修改過,所以進(jìn)行下一步交換操作是沒問題的.
這里一致,可能是沒改過.也可能是,改過,但是還原回來了?!
把 value 的值設(shè)為A的話, CAS 判定 value 為 A,此時(shí)可能確實(shí)value始終是A,也可能是value本來是A,被改成了B,又還原成了A …
ABA 問題就相當(dāng)于,買個(gè)手機(jī),買到的這個(gè)手機(jī),可能是新機(jī),也可能是翻新機(jī).
翻新機(jī): 二手的,被銷售商回收了,經(jīng)過一些翻新操作(把外殼換了,重新包裝).
ABA 這個(gè)情況,大部分情況下,其實(shí)是不會(huì)對代碼/邏輯產(chǎn)生太大影響的,但是不排除一些“極端情況”,也是可能造成影響的.
例子:
上述場景,概率非常低!!!一方面,恰好,滑稽這邊多按了幾次,產(chǎn)生多個(gè)扣款動(dòng)作了,另一方面,恰好在這個(gè)非常極限的時(shí)間內(nèi),有人轉(zhuǎn)賬了一樣的金額.
解決方案
針對當(dāng)前問題,采取的方案,就是加入一個(gè)版本號(hào).想象成,初始版本號(hào)是1,每次修改版本號(hào)都+1,然后進(jìn)行 CAS 的時(shí)候不是以金額為基準(zhǔn)了,而是以版本號(hào)為基準(zhǔn).此時(shí),版本號(hào)要是沒變,就是一定沒有發(fā)生改變(版本號(hào)是只能增長,不能降低的).