1、并發(fā)模型一般有兩類
阻塞方式 – 通過加鎖來實(shí)現(xiàn)資源并發(fā)
非阻塞方式 - 系統(tǒng)原語實(shí)現(xiàn)
I、死鎖 VS 活鎖
死鎖線程相互等待資源,導(dǎo)致無法執(zhí)行
活鎖線程相互謙讓資源,導(dǎo)致無法執(zhí)行 -- 饑餓場景,一直都搶不到資源
活鎖可以解開,而死鎖無法自動解開
II、自旋鎖 VS 互斥鎖
自旋鎖一般用在多核,當(dāng)搶占不到資源時(shí),自旋鎖會一直再輪詢搶占鎖;而互斥鎖,搶占不到時(shí),線程會休眠,等待喚醒。
從效率上看,自旋鎖沒有線程切換,效率要高,但一直占用CPU也會對資源浪費(fèi)。所以,對于長時(shí)間占用,單核的這些還是使用互斥鎖;多核,非常短時(shí)間占用的,則使用自旋鎖效率高點(diǎn)
java8 提供了 StampedLock 自旋鎖
其中非 阻塞方式(不使用鎖),由于高性能和高并發(fā),最近幾年越來越被推崇。
III、無干擾(obstruction-free)
IV、無鎖(lock-free)
最典型的無鎖用法,就是 CAS ,java并發(fā)集合中提供了大量的CAS 基礎(chǔ)的操作
ABA 問題
主要是線程T1 把 A 操作了 B,又操作回了A, 線程 T2 在第一次讀取時(shí),讀取的是A,第二次讀的是 更改后的A,這樣 CAS 操作就是ABA問題。
對于普通的系統(tǒng)基本類型,這沒有什么問題,但對于 復(fù)雜的數(shù)據(jù)結(jié)構(gòu)就可能有影響了。
非常類似 數(shù)據(jù)庫事務(wù)中的 幻讀,兩次操作之間,范圍沒有變,但再中間插入了記錄(或者刪除了數(shù)據(jù))
V、無等待(wait-free)
VI、synchronized vs volatile
volatile 只有可見性,即任何線程的修改可以看到;synchronized 保證可見性和操作原子性。
一個常見的比較 volatile 修飾 引用類型,和 AtomicReference 的區(qū)別:
AtomicReference 能夠保證里面所有的 域 同時(shí)刷新,被其他線程看到;而volatile 修飾的引用多個域 操作不能保證同時(shí)被處理,如果域數(shù)據(jù)之間有依賴就會出現(xiàn)問題
2、線程工作模式
I、并行工作者模式
一個主線程負(fù)責(zé)派發(fā),多個線程接受派發(fā)任務(wù)。傳統(tǒng)的多線程模型都是基于此,java的并發(fā)包大多也是這個模式。
優(yōu)點(diǎn):可以方便的增加和減少工作者。
缺點(diǎn):
a、存在共享資源的搶奪,導(dǎo)致多個線程交互的復(fù)雜度和效率的下降。解決的方法:
A> 非阻塞并發(fā)方式
比如 java的 Future,把阻塞的方法改成了非阻塞的方法(本質(zhì)上是交給另一個線程去執(zhí)行)
B> 可持久化數(shù)據(jù)結(jié)構(gòu)
這里的可持久化數(shù)據(jù)結(jié)構(gòu)是指一些 不可變的對象,在函數(shù)式編程語言比較常見,在Java中對應(yīng)的比如 String,CopyOnWriteArrayList
b、工作線程的順序未知
為了協(xié)調(diào)線程間的順序,java并發(fā)包中提供了很多資源控制項(xiàng),比如CountDownLatch/CycleBarrier/Semphore
II、流水線模式
流水線模式也稱為反應(yīng)器/事件驅(qū)動模式;使用流水線模式,最有名的就是tomcat;還有akka事件框架(spark調(diào)度使用);Netty的 EventLoops
一般使用非阻塞IO來實(shí)現(xiàn),非阻塞IO本質(zhì)上是一個用空間(代碼邏輯復(fù)雜度)換時(shí)間(IO)的策略,節(jié)省了大量阻塞IO模式下等待的過程。以網(wǎng)絡(luò)通訊來說,多個端口注冊到一個selector上,由這個selector去輪詢,這樣只需要一個線程無需切換,有兩個功效:
○、多個IO時(shí)可以在一個線程里處理,處理完一個,可以接著處理下一個,向流水線一樣,這些IO可以是類似觀察者,通過回調(diào)函數(shù)去派發(fā),也可以直接寫在代碼里面
○、因?yàn)樵谝粋€線程里,這個流水線沒有和其他線程共享數(shù)據(jù),所以數(shù)據(jù)是線程安全的,這和并行工作者有很大不同
a、actors 模式
AKKA框架
b、CSP 模式