突擊并發(fā)編程JUC系列演示代碼地址:
https://github.com/mtcarpenter/JavaTutorial
什么是 CAS 嗎?
CAS(Compare And Swap)
指比較并交換。CAS
算法CAS(V, E, N)
包含 3 個(gè)參數(shù),V 表示要更新的變量人断,E 表示預(yù)期的值术浪,N 表示新值祥得。在且僅在 V 值等于 E值時(shí)馒疹,才會(huì)將 V 值設(shè)為 N,如果 V 值和 E 值不同年局,則說明已經(jīng)有其他線程做了更新,當(dāng)前線程什么都不做咸产。最后某宪,CAS 返回當(dāng)前 V 的真實(shí)值。Concurrent
包下所有類底層都是依靠CAS
操作來實(shí)現(xiàn)锐朴,而sun.misc.Unsafe
為我們提供了一系列的CAS
操作兴喂。
CAS 有什么缺點(diǎn)?
-
ABA
問題 - 自旋問題
- 范圍不能靈活控制
對 CAS 中的 ABA 產(chǎn)生有解決方案嗎?
什么是 ABA 問題呢衣迷?多線程環(huán)境下畏鼓。線程 1 從內(nèi)存的V位置取出 A ,線程 2 也從內(nèi)存中取出 A壶谒,并將 V 位置的數(shù)據(jù)首先修改為 B云矫,接著又將 V 位置的數(shù)據(jù)修改為 A,線程 1 在進(jìn)行CAS
操作時(shí)會(huì)發(fā)現(xiàn)在內(nèi)存中仍然是 A汗菜,線程 1 操作成功让禀。盡管從線程 1 的角度來說,CAS
操作是成功的陨界,但在該過程中其實(shí) V 位置的數(shù)據(jù)發(fā)生了變化巡揍,線程 1 沒有感知到罷了,這在某些應(yīng)用場景下可能出現(xiàn)過程數(shù)據(jù)不一致的問題菌瘪。
可以版本號(hào)(version)來解決 ABA 問題的腮敌,在 atomic
包中提供了AtomicStampedReference
這個(gè)類,它是專門用來解決 ABA 問題的俏扩。
直達(dá)鏈接: AtomicStampedReference ABA 案例鏈接
CAS 自旋導(dǎo)致的問題糜工?
由于單次 CAS
不一定能執(zhí)行成功,所以 CAS
往往是配合著循環(huán)來實(shí)現(xiàn)的录淡,有的時(shí)候甚至是死循環(huán)捌木,不停地進(jìn)行重試,直到線程競爭不激烈的時(shí)候嫉戚,才能修改成功刨裆。
CPU 資源也是一直在被消耗的,這會(huì)對性能產(chǎn)生很大的影響彼水。所以這就要求我們崔拥,要根據(jù)實(shí)際情況來選擇是否使用 CAS
,在高并發(fā)的場景下凤覆,通常 CAS
的效率是不高的链瓦。
CAS 范圍不能靈活控制
不能靈活控制線程安全的范圍。只能針對某一個(gè)盯桦,而不是多個(gè)共享變量的慈俯,不能針對多個(gè)共享變量同時(shí)進(jìn)行 CAS
操作,因?yàn)檫@多個(gè)變量之間是獨(dú)立的拥峦,簡單的把原子操作組合到一起贴膘,并不具備原子性。
什么是 AQS 嗎略号?
AbstractQueuedSynchronizer
抽象同步隊(duì)列簡稱AQS
刑峡,它是實(shí)現(xiàn)同步器的基礎(chǔ)組件洋闽,并發(fā)包中鎖的底層就是使用AQS
實(shí)現(xiàn)的。AQS
定義了一套多線程訪問共享資源的同步框架突梦,許多同步類的實(shí)現(xiàn)都依賴于它诫舅,例如常用的Synchronized
、ReentrantLock
宫患、ReentrantReadWriteLock
刊懈、Semaphore
、CountDownLatch
等娃闲。該框架下的鎖會(huì)先嘗試以CAS
樂觀鎖去獲取鎖虚汛,如果獲取不到,則會(huì)轉(zhuǎn)為悲觀鎖(如RetreenLock
)皇帮。
了解 AQS 共享資源的方式嗎卷哩?
- 獨(dú)占式:只有一個(gè)線程能執(zhí)行,具體的Java實(shí)現(xiàn)有
ReentrantLock
玲献。 - 共享式:多個(gè)線程可同時(shí)執(zhí)行殉疼,具體的Java實(shí)現(xiàn)有
Semaphore
和CountDownLatch
梯浪。
Atomic 原子更新
Java
從 JDK1.5
開始提供了 java.util.concurrent.atomic
包捌年,方便程序員在多線程環(huán) 境下,無鎖的進(jìn)行原子操作挂洛。在 Atomic
包里一共有 12 個(gè)類礼预,四種原子更新方式,分別是原子更新基本類型
虏劲,原子更新數(shù)組
托酸,原子更新引用
和原子更新字段
。在 JDK 1.8
之后又新增幾個(gè)原子類柒巫。如下如:
針對思維導(dǎo)圖知識(shí)點(diǎn)在前面的章節(jié)都進(jìn)行了理論+實(shí)踐的講解励堡,到達(dá)地址如下:
突擊并發(fā)編程JUC系列-原子更新AtomicLong
突擊并發(fā)編程JUC系列-數(shù)組類型AtomicLongArray
突擊并發(fā)編程JUC系列-原子更新字段類AtomicStampedReference
突擊并發(fā)編程JUC系列-JDK1.8 擴(kuò)展類型 LongAdder
列舉幾個(gè)AtomicLong
的常用方法
-
long getAndIncrement()
:以原子方式將當(dāng)前值加1,注意堡掏,返回的是舊值应结。(i++) -
long incrementAndGet()
:以原子方式將當(dāng)前值加1,注意泉唁,返回的是新值鹅龄。(++i) -
long getAndDecrement()
:以原子方式將當(dāng)前值減 1,注意亭畜,返回的是舊值 扮休。(i--) -
long decrementAndGet()
:以原子方式將當(dāng)前值減 1,注意拴鸵,返回的是新值 玷坠。(--i) -
long addAndGet(int delta)
:以原子方式將輸入的數(shù)值與實(shí)例中的值(AtomicLong
里的value
)相加蜗搔,并返回結(jié)果
說說 AtomicInteger 和 synchronized 的異同點(diǎn)?
相同點(diǎn)
- 都是線程安全
不同點(diǎn)
- 1八堡、背后原理
synchronized 背后的 monitor 鎖碍扔。在執(zhí)行同步代碼之前,需要首先獲取到 monitor 鎖秕重,執(zhí)行完畢后不同,再釋放鎖。原子類溶耘,線程安全的原理是利用了 CAS 操作二拐。 - 2、使用范圍
原子類使用范圍是比較局限的,一個(gè)原子類僅僅是一個(gè)對象凳兵,不夠靈活百新。而synchronized
的使用范圍要廣泛得多。比如說 synchronized 既可以修飾一個(gè)方法庐扫,又可以修飾一段代碼饭望,相當(dāng)于可以根據(jù)我們的需要,非常靈活地去控制它的應(yīng)用范圍 - 3形庭、粒度
原子變量的粒度是比較小的铅辞,它可以把競爭范圍縮小到變量級(jí)別。通常情況下萨醒,synchronized
鎖的粒度都要大于原子變量的粒度斟珊。 - 4、性能
synchronized
是一種典型的悲觀鎖富纸,而原子類恰恰相反囤踩,它利用的是樂觀鎖。
原子類和 volatile 有什么異同晓褪?
-
volatile
可見性問題 - 解決原子性問題
AtomicLong 可否被 LongAdder 替代堵漱?
有了更高效的 LongAdder
,那AtomicLong
可否不使用了呢涣仿?是否凡是用到 AtomicLong
的地方勤庐,都可以用LongAdder
替換掉呢?答案是不是的变过,這需要區(qū)分場景埃元。
LongAdder
只提供了 add
、increment
等簡單的方法媚狰,適合的是統(tǒng)計(jì)求和計(jì)數(shù)的場景岛杀,場景比較單一,而 AtomicLong
還具有 compareAndSet
等高級(jí)方法崭孤,可以應(yīng)對除了加減之外的更復(fù)雜的需要CAS
的場景类嗤。
結(jié)論:如果我們的場景僅僅是需要用到加和減操作的話糊肠,那么可以直接使用更高效的 LongAdder
,但如果我們需要利用 CAS
比如compareAndSet
等操作的話遗锣,就需要使用 AtomicLong
來完成货裹。
并發(fā)工具
CountDownLatch
CountDownLatch
基于線程計(jì)數(shù)器來實(shí)現(xiàn)并發(fā)訪問控制,主要用于主線程等待其他子線程都執(zhí)行完畢后執(zhí)行相關(guān)操作精偿。其使用過程為:在主線程中定義CountDownLatch
弧圆,并將線程計(jì)數(shù)器的初始值設(shè)置為子線程的個(gè)數(shù),多個(gè)子線程并發(fā)執(zhí)行笔咽,每個(gè)子線程在執(zhí)行完畢后都會(huì)調(diào)用countDown
函數(shù)將計(jì)數(shù)器的值減1搔预,直到線程計(jì)數(shù)器為0,表示所有的子線程任務(wù)都已執(zhí)行完畢叶组,此時(shí)在CountDownLatch
上等待的主線程將被喚醒并繼續(xù)執(zhí)行拯田。
CyclicBarrier
CyclicBarrier
(循環(huán)屏障)是一個(gè)同步工具,可以實(shí)現(xiàn)讓一組線程等待至某個(gè)狀態(tài)之后再全部同時(shí)執(zhí)行甩十。在所有等待線程都被釋放之后船庇,CyclicBarrier
可以被重用。CyclicBarrier
的運(yùn)行狀態(tài)叫作Barrier
狀態(tài)侣监,在調(diào)用await
方法后鸭轮,線程就處于Barrier
狀態(tài)。
CyclicBarrier
中最重要的方法是await方法达吞,它有兩種實(shí)現(xiàn)张弛。
-
public int await()
:掛起當(dāng)前線程直到所有線程都為Barrier狀態(tài)再同時(shí)執(zhí)行后續(xù)的任務(wù)荒典。 -
public int await(long timeout, TimeUnit unit)
:設(shè)置一個(gè)超時(shí)時(shí)間酪劫,在超時(shí)時(shí)間過后,如果還有線程未達(dá)到Barrier
狀態(tài)寺董,則不再等待覆糟,讓達(dá)到Barrier狀態(tài)的線程繼續(xù)執(zhí)行后續(xù)的任務(wù)。
Semaphore
Semaphore
指信號(hào)量遮咖,用于控制同時(shí)訪問某些資源的線程個(gè)數(shù)滩字,具體做法為通過調(diào)用acquire()
獲取一個(gè)許可,如果沒有許可御吞,則等待麦箍,在許可使用完畢后通過release()
釋放該許可,以便其他線程使用陶珠。
CyclicBarrier 和 CountdownLatch 有什么異同挟裂?
相同點(diǎn):都能阻塞一個(gè)或一組線程,直到某個(gè)預(yù)設(shè)的條件達(dá)成發(fā)生揍诽,再統(tǒng)一出發(fā)诀蓉。
但是它們也有很多不同點(diǎn)栗竖,具體如下。
- 作用對象不同:
CyclicBarrier
要等固定數(shù)量的線程都到達(dá)了柵欄位置才能繼續(xù)執(zhí)行渠啤,而CountDownLatch
只需等待數(shù)字倒數(shù)到 0狐肢,也就是說CountDownLatch
作用于事件,但CyclicBarrier
作用于線程沥曹;CountDownLatch
是在調(diào)用了countDown
方法之后把數(shù)字倒數(shù)減 1份名,而CyclicBarrier
是在某線程開始等待后把計(jì)數(shù)減 1。 - 可重用性不同:
CountDownLatch
在倒數(shù)到 0 并且觸發(fā)門閂打開后妓美,就不能再次使用了同窘,除非新建一個(gè)新的實(shí)例;而CyclicBarrier
可以重復(fù)使用部脚。CyclicBarrier
還可以隨時(shí)調(diào)用 reset 方法進(jìn)行重置想邦,如果重置時(shí)有線程已經(jīng)調(diào)用了 await 方法并開始等待,那么這些線程則會(huì)拋出BrokenBarrierException
異常委刘。 - 執(zhí)行動(dòng)作不同:
CyclicBarrier
有執(zhí)行動(dòng)作barrierAction
丧没,而CountDownLatch
沒這個(gè)功能。
CountDownLatch锡移、CyclicBarrier呕童、Semaphore的區(qū)別如下。
-
CountDownLatch
和CyclicBarrier
都用于實(shí)現(xiàn)多線程之間的相互等待淆珊,但二者的關(guān)注點(diǎn)不同夺饲。CountDownLatch
主要用于主線程等待其他子線程任務(wù)均執(zhí)行完畢后再執(zhí)行接下來的業(yè)務(wù)邏輯單元,而CyclicBarrier
主要用于一組線程互相等待大家都達(dá)到某個(gè)狀態(tài)后施符,再同時(shí)執(zhí)行接下來的業(yè)務(wù)邏輯單元往声。此外,CountDownLatch
是不可以重用的戳吝,而CyclicBarrier
是可以重用的浩销。 -
Semaphore
和Java
中的鎖功能類似,主要用于控制資源的并發(fā)訪問听哭。
locks
公平鎖與非公平鎖
ReentrantLock
支持公平鎖和非公平鎖兩種方式慢洋。公平鎖指鎖的分配和競爭機(jī)制是公平的,即遵循先到先得原則陆盘。非公平鎖指JVM
遵循隨機(jī)普筹、就近原則分配鎖的機(jī)制。ReentrantLock
通過在構(gòu)造函數(shù)ReentrantLock(boolean fair)
中傳遞不同的參數(shù)來定義不同類型的鎖隘马,默認(rèn)的實(shí)現(xiàn)是非公平鎖太防。這是因?yàn)椋枪芥i雖然放棄了鎖的公平性祟霍,但是執(zhí)行效率明顯高于公平鎖杏头。如果系統(tǒng)沒有特殊的要求盈包,一般情況下建議使用非公平鎖。
synchronized 和 lock 有什么區(qū)別醇王?
-
synchronized
可以給類呢燥,方法,代碼塊加鎖寓娩,而lock
只能給代碼塊加鎖叛氨。 -
synchronized
不需要手動(dòng)獲取鎖和釋放鎖,使用簡單棘伴,發(fā)生異常會(huì)自動(dòng)釋放鎖寞埠,不會(huì)造成死鎖,而lock
需要手動(dòng)自己加鎖和釋放鎖焊夸,如果使用不當(dāng)沒有unLock
去釋放鎖仁连,就會(huì)造成死鎖。 - 通過
lock
可以知道有沒有成功獲取鎖阱穗,而synchronized
無法辦到饭冬。
synchronized 和 Lock 如何選擇?
-
synchronized
和Lock
都是用來保護(hù)資源線程安全的揪阶。 - 都保證了可見性和互斥性昌抠。
-
synchronized
和ReentrantLock
都擁有可重入的特點(diǎn)。
不同點(diǎn):
- 用法(
lock
需要配合finally
) -
ReentrantLock
可響應(yīng)中斷鲁僚、可輪回炊苫,為處理鎖提供了更多的靈活性 -
ReentrantLock
通過Condition
可以綁定多個(gè)條件 - 加解鎖順序()
-
synchronized
鎖不夠靈活 - 是否可以設(shè)置公平/非公平
- 二者的底層實(shí)現(xiàn)不一樣:
synchronized
是同步阻塞,采用的是悲觀并發(fā)策略冰沙;Lock
是同步非阻塞侨艾,采用的是樂觀并發(fā)策略。
使用
- 如果能不用最好既不使用
Lock
也不使用synchronized
倦淀。 - 如果
synchronized
關(guān)鍵字適合你的程序蒋畜,這樣可以減少編寫代碼的數(shù)量,減少出錯(cuò)的概率 - 如果特別需要
Lock
的特殊功能撞叽,比如嘗試獲取鎖、可中斷插龄、超時(shí)功能等愿棋,才使用Lock
。
Lock接口的主要方法
-
void lock()
:獲取鎖均牢,調(diào)用該方法當(dāng)前線程將會(huì)獲取鎖糠雨,當(dāng)鎖獲得后,從該方法返回 -
void lockInterruptibly() throws InterruptedException
:可中斷地獲取鎖徘跪,和lock
方法地不同之處在于該方法會(huì)響應(yīng)中斷甘邀,即在鎖的獲取中可以中斷當(dāng)前線程 -
boolean tryLock()
: 嘗試非阻塞地獲取鎖琅攘,調(diào)用該方法后立刻返回,如果能夠獲取則返回 true 否則 返回false -
boolean tryLock(long time, TimeUnit unit)
:超時(shí)地獲取鎖松邪,當(dāng)前線程在以下 3 種情況下會(huì)返回: - 當(dāng)前線程在超時(shí)時(shí)間內(nèi)獲得了鎖
- 當(dāng)前線程在超時(shí)時(shí)間被中斷
- 超時(shí)時(shí)間結(jié)束后坞琴,返回 false
-
void unlock()
: 釋放鎖 -
Condition newCondition()
:獲取鎖等待通知組件,該組件和當(dāng)前的鎖綁定逗抑,當(dāng)前線程只有獲得了鎖剧辐,才能調(diào)用該組件的wait()
方法,而調(diào)用后邮府,當(dāng)前線程將釋放鎖荧关。
tryLock、lock和lockInterruptibly的區(qū)別
tryLock
褂傀、lock
和lockInterruptibly
的區(qū)別如下忍啤。
-
tryLock
若有可用鎖,則獲取該鎖并返回true仙辟,否則返回false檀轨,不會(huì)有延遲或等待;tryLock(long timeout, TimeUnit unit)
可以增加時(shí)間限制欺嗤,如果超過了指定的時(shí)間還沒獲得鎖参萄,則返回 false。 -
lock
若有可用鎖煎饼,則獲取該鎖并返回true讹挎,否則會(huì)一直等待直到獲取可用鎖。 - 在鎖中斷時(shí)
lockInterruptibly
會(huì)拋出異常吆玖,lock
不會(huì)筒溃。
ReentrantReadWriteLock 讀寫鎖的獲取規(guī)則
要么是一個(gè)或多個(gè)線程同時(shí)有讀鎖,要么是一個(gè)線程有寫鎖沾乘,但是兩者不會(huì)同時(shí)出現(xiàn)怜奖。也可以總結(jié)為:讀讀共享、其他都互斥(寫寫互斥翅阵、讀寫互斥歪玲、寫讀互斥)
ReentrantLock
適用于一般場合,ReadWriteLock
適用于讀多寫少的情況掷匠,合理使用可以進(jìn)一步提高并發(fā)效率滥崩。
讀鎖應(yīng)該插隊(duì)嗎?什么是讀寫鎖的升降級(jí)讹语?
ReentrantReadWriteLock
的實(shí)現(xiàn)選擇了“不允許插隊(duì)”的策略钙皮,這就大大減小了發(fā)生“饑餓”的概率。
插隊(duì)策略
- 公平策略下,只要隊(duì)列里有線程已經(jīng)在排隊(duì)短条,就不允許插隊(duì)导匣。
- 非公平策略下:
- 如果允許讀鎖插隊(duì),那么由于讀鎖可以同時(shí)被多個(gè)線程持有茸时,所以可能造成源源不斷的后面的線程一直插隊(duì)成功贡定,導(dǎo)致讀鎖一直不能完全釋放,從而導(dǎo)致寫鎖一直等待屹蚊,為了防止“饑餓”厕氨,在等待隊(duì)列的頭結(jié)點(diǎn)是嘗試獲取寫鎖的線程的時(shí)候,不允許讀鎖插隊(duì)汹粤。
- 寫鎖可以隨時(shí)插隊(duì)命斧,因?yàn)閷戞i并不容易插隊(duì)成功,寫鎖只有在當(dāng)前沒有任何其他線程持有讀鎖和寫鎖的時(shí)候嘱兼,才能插隊(duì)成功国葬,同時(shí)寫鎖一旦插隊(duì)失敗就會(huì)進(jìn)入等待隊(duì)列,所以很難造成“饑餓”的情況芹壕,允許寫鎖插隊(duì)是為了提高效率汇四。
升降級(jí)策略:只能從寫鎖降級(jí)為讀鎖,不能從讀鎖升級(jí)為寫鎖踢涌。
怎么防止死鎖通孽?
- 盡量使用
tryLock(long timeout,TimeUnit unit)
的方法(ReentrantLock 、ReenttranReadWriteLock
)設(shè)置超時(shí)時(shí)間睁壁,超時(shí)可以退出防止死鎖背苦。 - 盡量使用
java.util.concurrent
并發(fā)類代替手寫鎖。 - 盡量降低鎖的使用粒度潘明,盡量不要幾個(gè)功能用同一把鎖行剂。
- 盡量減少同步的代碼塊。
Condition 類和 Object 類鎖方法區(qū)別區(qū)別
-
Condition
類的awiat
方法和Object
類的wait
方法等效 -
Condition
類的signal
方法和Object
類的notify
方法等效 -
Condition
類的signalAll
方法和Object
類的notifyAll
方法等效 -
ReentrantLock
類可以喚醒指定條件的線程钳降,而 object 的喚醒是隨機(jī)的
并發(fā)容器
為什么 ConcurrentHashMap 比 HashTable 效率要高厚宰?
-
HashTable
使用一把鎖(鎖住整個(gè)鏈表結(jié)構(gòu))處理并發(fā)問題,多個(gè)線程競爭一把鎖遂填,容易阻塞铲觉; -
ConcurrentHashMap
-
JDK 1.7
中使用分段鎖(ReentrantLock + Segment + HashEntry
),相當(dāng)于把一個(gè)HashMap
分成多個(gè)段城菊,每段分配一把鎖备燃,這樣支持多線程訪問。鎖粒度:基于 Segment凌唬,包含多個(gè)HashEntry
。 -
JDK 1.8
中使用CAS + synchronized + Node + 紅黑樹
。鎖粒度:Node(首結(jié)點(diǎn))(實(shí)現(xiàn)Map.Entry
)客税。鎖粒度降低了况褪。
-
ConcurrentHashMap JDK 1.7/JDK 1.8
JDK 1.7 結(jié)構(gòu)
JDK 1.7
中的ConcurrentHashMap
內(nèi)部進(jìn)行了 Segment
分段,Segment
繼承了 ReentrantLock
更耻,可以理解為一把鎖测垛,各個(gè) Segment
之間都是相互獨(dú)立上鎖的,互不影響秧均。
相比于之前的 Hashtable
每次操作都需要把整個(gè)對象鎖住而言食侮,大大提高了并發(fā)效率。因?yàn)樗逆i與鎖之間是獨(dú)立的目胡,而不是整個(gè)對象只有一把鎖锯七。
每個(gè) Segment 的底層數(shù)據(jù)結(jié)構(gòu)與 HashMap
類似,仍然是數(shù)組和鏈表組成的拉鏈法結(jié)構(gòu)誉己。默認(rèn)有 0~15 共 16 個(gè) Segment
眉尸,所以最多可以同時(shí)支持 16 個(gè)線程并發(fā)操作(操作分別分布在不同的 Segment
上)。16 這個(gè)默認(rèn)值可以在初始化的時(shí)候設(shè)置為其他值巨双,但是一旦確認(rèn)初始化以后噪猾,是不可以擴(kuò)容的。
JDK 1.8 結(jié)構(gòu)
圖中的節(jié)點(diǎn)有三種類型:
- 第一種是最簡單的筑累,空著的位置代表當(dāng)前還沒有元素來填充袱蜡。
- 第二種就是和
HashMap
非常類似的拉鏈法結(jié)構(gòu),在每一個(gè)槽中會(huì)首先填入第一個(gè)節(jié)點(diǎn)慢宗,但是后續(xù)如果計(jì)算出相同的 Hash 值坪蚁,就用鏈表的形式往后進(jìn)行延伸。 - 第三種結(jié)構(gòu)就是紅黑樹結(jié)構(gòu)婆廊,這是 Java 7 的
ConcurrentHashMap
中所沒有的結(jié)構(gòu)迅细,在此之前我們可能也很少接觸這樣的數(shù)據(jù)結(jié)構(gòu)
鏈表長度大于某一個(gè)閾值(默認(rèn)為 8),滿足容量從鏈表的形式轉(zhuǎn)化為紅黑樹的形式淘邻。
紅黑樹是每個(gè)節(jié)點(diǎn)都帶有顏色屬性的二叉查找樹茵典,顏色為紅色或黑色,紅黑樹的本質(zhì)是對二叉查找樹 BST 的一種平衡策略宾舅,我們可以理解為是一種平衡二叉查找樹统阿,查找效率高,會(huì)自動(dòng)平衡筹我,防止極端不平衡從而影響查找效率的情況發(fā)生扶平,紅黑樹每個(gè)節(jié)點(diǎn)要么是紅色,要么是黑色蔬蕊,但根節(jié)點(diǎn)永遠(yuǎn)是黑色的结澄。
ConcurrentHashMap 中 get 的過程
- 計(jì)算 Hash 值,并由此值找到對應(yīng)的槽點(diǎn);
- 如果數(shù)組是空的或者該位置為 null麻献,那么直接返回 null 就可以了们妥;
- 如果該位置處的節(jié)點(diǎn)剛好就是我們需要的,直接返回該節(jié)點(diǎn)的值勉吻;
- 如果該位置節(jié)點(diǎn)是紅黑樹或者正在擴(kuò)容监婶,就用 find 方法繼續(xù)查找;
- 否則那就是鏈表齿桃,就進(jìn)行遍歷鏈表查找
ConcurrentHashMap 中 put 的過程
- 判斷
Node[]
數(shù)組是否初始化惑惶,沒有則進(jìn)行初始化操作 - 通過
hash
定位數(shù)組的索引坐標(biāo),是否有Node
節(jié)點(diǎn)短纵,如果沒有則使用CAS
進(jìn)行添加(鏈表的頭節(jié)點(diǎn))带污,添加失敗則進(jìn)入下次循環(huán)。 - 檢查到內(nèi)部正在擴(kuò)容踩娘,就幫助它一塊擴(kuò)容刮刑。
- 如果 f != null ,則使用
synchronized
鎖住 f 元素(鏈表/紅黑二叉樹的頭元素) - 如果是
Node
(鏈表結(jié)構(gòu))則執(zhí)行鏈表的添加操作 - 如果是
TreeNode
(樹形結(jié)構(gòu))則執(zhí)行樹添加操作养渴。
- 如果是
- 判斷鏈表長度已經(jīng)達(dá)到臨界值 8 雷绢,當(dāng)然這個(gè) 8 是默認(rèn)值,大家也可以去做調(diào)整理卑,當(dāng)節(jié)點(diǎn)數(shù)超過這個(gè)值就需要把鏈表轉(zhuǎn)換為樹結(jié)構(gòu)翘紊。
什么是阻塞隊(duì)列?
阻塞隊(duì)列(BlockingQueue
)是一個(gè)支持兩個(gè)附加操作的隊(duì)列。這兩個(gè)附加的操作支持阻塞的插入和移除方法藐唠。
- 支持阻塞的插入方法:意思是當(dāng)隊(duì)列滿時(shí)帆疟,隊(duì)列會(huì)阻塞插入元素的線程,直到隊(duì)列不滿宇立。
- 支持阻塞的移除方法:意思是在隊(duì)列為空時(shí)踪宠,獲取元素的線程會(huì)等待隊(duì)列變?yōu)榉强铡?/li>
阻塞隊(duì)列常用于生產(chǎn)者和消費(fèi)者的場景,生產(chǎn)者是向隊(duì)列里添加元素的線程妈嘹,消費(fèi)者是從隊(duì)列里取元素的線程柳琢。阻塞隊(duì)列就是生產(chǎn)者用來存放元素、消費(fèi)者用來獲取元素的容器润脸。
列舉幾個(gè)常見的阻塞隊(duì)列
-
ArrayBlockingQueue
:一個(gè)由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列柬脸。 -
LinkedBlockingQueue
:一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列。 -
PriorityBlockingQueue
:一個(gè)支持優(yōu)先級(jí)排序的無界阻塞隊(duì)列毙驯。 -
DelayQueue
:一個(gè)使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無界阻塞隊(duì)列倒堕。 -
SynchronousQueue
:一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列。 -
LinkedTransferQueue
:一個(gè)由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列爆价。 -
LinkedBlockingDeque
:一個(gè)由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列垦巴。
線程池
使用線程池的優(yōu)勢
Java 中的線程池是運(yùn)用場景最多的并發(fā)框架媳搪,幾乎所有需要異步或并發(fā)執(zhí)行任務(wù)的程序都可以使用線程池。
- 降低資源消耗魂那。 通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗蛾号。
- 提高響應(yīng)速度稠项。 當(dāng)任務(wù)到達(dá)時(shí)涯雅,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。
- 提高線程的可管理性展运。 線程是稀缺資源活逆,如果無限制地創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源拗胜,還會(huì)降低系統(tǒng)的穩(wěn)定性蔗候,使用線程池可以進(jìn)行統(tǒng)一分配、調(diào)優(yōu)和監(jiān)控埂软。但是锈遥,要做到合理利用線程池,必須對其實(shí)現(xiàn)原理了如指掌勘畔。
線程池的實(shí)現(xiàn)原理
當(dāng)提交一個(gè)新任務(wù)到線程池時(shí)所灸,線程池的處理流程如下:
- 線程池判斷核心線程池里的線程是否都在執(zhí)行任務(wù)。如果不是炫七,則創(chuàng)建一個(gè)新的工作線程來執(zhí)行任務(wù)爬立。如果核心線程池里的線程都在執(zhí)行任務(wù),則進(jìn)入下個(gè)流程万哪。
- 線程池判斷工作隊(duì)列是否已經(jīng)滿侠驯。如果工作隊(duì)列沒有滿,則將新提交的任務(wù)存儲(chǔ)在這個(gè)工作隊(duì)列里奕巍。如果工作隊(duì)列滿了吟策,則進(jìn)入下個(gè)流程。
-
線程池判斷線程池的線程是否都處于工作狀態(tài)的止。如果沒有檩坚,則創(chuàng)建一個(gè)新的工作線程來執(zhí)行任務(wù)。如果已經(jīng)滿了冲杀,則交給飽和策略來處理這個(gè)任務(wù)效床。
ThreadPoolExecutor
執(zhí)行execute()
方法的示意圖 如下:
ThreadPoolExecutor
執(zhí)行execute
方法分下面 4 種情況:
- 1、如果當(dāng)前運(yùn)行的線程少于
corePoolSize
权谁,則創(chuàng)建新線程來執(zhí)行任務(wù)(注意剩檀,執(zhí)行這一步驟需要獲取全局鎖)。 - 2旺芽、如果運(yùn)行的線程等于或多于
corePoolSize
沪猴,則將任務(wù)加入BlockingQueue
辐啄。 - 3、如果無法將任務(wù)加入
BlockingQueue
(隊(duì)列已滿)运嗜,則創(chuàng)建新的線程來處理任務(wù)(注意壶辜,執(zhí)行這一步驟需要獲取全局鎖)廓鞠。 - 4刻帚、如果創(chuàng)建新線程將使當(dāng)前運(yùn)行的線程超出
maximumPoolSize
,任務(wù)將被拒絕浓恶,并調(diào)用RejectedExecutionHandler.rejectedExecution()
方法奋救。
ThreadPoolExecutor
采取上述步驟的總體設(shè)計(jì)思路岭参,是為了在執(zhí)行execute()方法時(shí),盡可能地避免獲取全局鎖(那將會(huì)是一個(gè)嚴(yán)重的可伸縮瓶頸)尝艘。在ThreadPoolExecutor
完成預(yù)熱之后(當(dāng)前運(yùn)行的線程數(shù)大于等于corePoolSize
)演侯,幾乎所有的execute()
方法調(diào)用都是執(zhí)行步驟 2,而步驟2不需要獲取全局鎖背亥。
創(chuàng)建線程有三種方式:
- 繼承 Thread 重寫 run 方法
- 實(shí)現(xiàn) Runnable 接口
- 實(shí)現(xiàn) Callable 接口 (有返回值)
線程有哪些狀態(tài)秒际?
-
NEW(初始)
,新建狀態(tài)狡汉,線程被創(chuàng)建出來娄徊,但尚未啟動(dòng)時(shí)的線程狀態(tài); -
RUNNABLE(就緒狀態(tài))
轴猎,表示可以運(yùn)行的線程狀態(tài)嵌莉,它可能正在運(yùn)行,或者是在排隊(duì)等待操作系統(tǒng)給它分配 CPU 資源捻脖; -
BLOCKED(阻塞)
锐峭,阻塞等待鎖的線程狀態(tài),表示處于阻塞狀態(tài)的線程正在等待監(jiān)視器鎖可婶,比如等待執(zhí)行synchronized
代碼塊或者使用synchronized
標(biāo)記的方法沿癞; -
WAITING(等待)
,等待狀態(tài)矛渴,一個(gè)處于等待狀態(tài)的線程正在等待另一個(gè)線程執(zhí)行某個(gè)特定的動(dòng)作椎扬,比如,一個(gè)線程調(diào)用了Object.wait()
方法具温,那它就在等待另一個(gè)線程調(diào)用Object.notify()
或Object.notifyAll()
方法蚕涤; -
TIMED_WAITING(超時(shí)等待)
,計(jì)時(shí)等待狀態(tài)铣猩,和等待狀態(tài)(WAITING)
類似揖铜,它只是多了超時(shí)時(shí)間,比如調(diào)用了有超時(shí)時(shí)間設(shè)置的方法Object.wait(long timeout)
和Thread.join(long timeout)
等這些方法時(shí)达皿,它才會(huì)進(jìn)入此狀態(tài)天吓; -
TERMINATED
贿肩,終止?fàn)顟B(tài),表示線程已經(jīng)執(zhí)行完成龄寞。
線程池的狀態(tài)有那些汰规?
-
running
:這是最正常的狀態(tài),接受新的任務(wù)物邑,處理等待隊(duì)列中的任務(wù)溜哮。 -
shutdown
:不接受新的任務(wù)提交,但是會(huì)繼續(xù)處理等待隊(duì)列中的任務(wù)拂封。 -
stop
:不接受新的任務(wù)提交茬射,不再處理等待隊(duì)列中的任務(wù),中斷正在執(zhí)行任務(wù)的線程冒签。 -
tidying
:所有的任務(wù)都銷毀了,workcount
為 0钟病,線程池的狀態(tài)再轉(zhuǎn)換 tidying 狀態(tài)時(shí)萧恕,會(huì)執(zhí)行鉤子方法terminated()
。 -
terminated
:terminated()
方法結(jié)束后肠阱,線程池的狀態(tài)就會(huì)變成這個(gè)票唆。
線程池中 sumbit() 和 execute() 方法有什么區(qū)別?
-
execute()
: 只能執(zhí)行Runable
類型的任務(wù)屹徘。 -
submit()
可以執(zhí)行Runable
和Callable
類型的任務(wù)走趋。
? Callable
類型的任務(wù)可以獲取執(zhí)行的返回值,而 Runnable
執(zhí)行無返回值噪伊。
線程池創(chuàng)建的方式
-
newSingleThreadExecutor()
: 他的特點(diǎn)是在于線程數(shù)目被限制位1:操作一個(gè)無界的工作隊(duì)列簿煌,所以它保證了所有的任務(wù)的都是順序執(zhí)行,最多會(huì)有一個(gè)任務(wù)處于活動(dòng)狀態(tài)鉴吹,并且不允許使用者改動(dòng)線程池實(shí)例姨伟,因此可以避免其改變線程數(shù)目。 -
newCachedThreadPool()
:它是一種用來處理大量短時(shí)間工作任務(wù)的線程豆励,具有幾個(gè)鮮明的特點(diǎn)夺荒,它會(huì)試圖緩存線程并重用,當(dāng)無緩存線程可用時(shí)良蒸,就會(huì)創(chuàng)建新的工作線程技扼,如果線程閑置的時(shí)間超過 60 秒,則被終止并移除緩存嫩痰;長時(shí)間閑置時(shí)剿吻,這種線程池不會(huì)消耗什么資源,其內(nèi)部使用synchronousQueue
作為工作隊(duì)列始赎。 -
newFixedThreadPool(int nThreads)
:重用指定數(shù)目 nThreads 的線程和橙,其背后使用的無界的工作隊(duì)列仔燕,任何時(shí)候最后有 nThreads 個(gè)工作線程活動(dòng)的,這意味著 如果任務(wù)數(shù)量超過了活動(dòng)隊(duì)列數(shù)目魔招,將在工作隊(duì)列中等待空閑線程出現(xiàn)晰搀,如果有工作線程退出,將會(huì)有新的工作線程被創(chuàng)建办斑,以補(bǔ)足指定的數(shù)目 nThreads外恕。 -
newSingleThreadScheduledExecutor()
: 創(chuàng)建單線程池,返回ScheduleExecutorService
可以進(jìn)行定時(shí)或周期性的工作強(qiáng)度乡翅。 -
newScheduleThreadPool(int corePoolSize)
: 和newSingleThreadSceduleExecutor()
類似鳞疲,創(chuàng)建的ScheduledExecutorService
可以進(jìn)行定時(shí)或周期的工作調(diào)度,區(qū)別在于單一工作線程還是工作線程蠕蚜。 -
newWorkStrealingPool(int parallelism)
:這是一個(gè)經(jīng)常被人忽略的線程池尚洽,Java 8 才加入這個(gè)創(chuàng)建方法,其內(nèi)部會(huì)構(gòu)建ForkJoinPool
利用work-strealing
算法 并行的處理任務(wù)靶累,不保證處理順序腺毫。 -
ThreadPollExecutor
: 是最原始的線程池創(chuàng)建,上面 1-3 創(chuàng)建方式 都是對ThreadPoolExecutor
的封裝挣柬。
上面 7 種創(chuàng)建方式中潮酒,前 6 種 通過Executors
工廠方法創(chuàng)建,ThreadPoolExecutor
手動(dòng)創(chuàng)建邪蛔。
ThreadPollExecutor 構(gòu)造方法
下面介紹下 ThreadPoolExecutor
接收 7 個(gè)參數(shù)的構(gòu)造方法
/**
* 用給定的初始參數(shù)創(chuàng)建一個(gè)新的ThreadPoolExecutor急黎。
*/
public ThreadPoolExecutor(int corePoolSize,//線程池的核心線程數(shù)量
int maximumPoolSize,//線程池的最大線程數(shù)
long keepAliveTime,//當(dāng)線程數(shù)大于核心線程數(shù)時(shí),多余的空閑線程存活的最長時(shí)間
TimeUnit unit,//時(shí)間單位
BlockingQueue<Runnable> workQueue,//任務(wù)隊(duì)列
ThreadFactory threadFactory,//線程工廠
RejectedExecutionHandler handler//拒絕策略
)
-
corePoolSize
: 核心線程數(shù)線程數(shù)定義了最小可以同時(shí)運(yùn)行的線程數(shù)量侧到。 -
maximumPoolSize
: 當(dāng)隊(duì)列中存放的任務(wù)達(dá)到隊(duì)列容量的時(shí)候勃教,當(dāng)前可以同時(shí)運(yùn)行的線程數(shù)量變?yōu)樽畲缶€程數(shù)。 -
workQueue
: 當(dāng)新任務(wù)來的時(shí)候會(huì)先判斷當(dāng)前運(yùn)行的線程數(shù)量是否達(dá)到核心線程數(shù)床牧,如果達(dá)到的話荣回,信任就會(huì)被存放在隊(duì)列中。 -
keepAliveTime
:線程活動(dòng)保持時(shí)間,當(dāng)線程池中的線程數(shù)量大于corePoolSize
的時(shí)候戈咳,如果這時(shí)沒有新的任務(wù)提交心软,核心線程外的線程不會(huì)立即銷毀,而是會(huì)等待著蛙,直到等待的時(shí)間超過了keepAliveTime
才會(huì)被回收銷毀删铃; -
unit
:keepAliveTime
參數(shù)的時(shí)間單位。 -
threadFactory
: 任務(wù)隊(duì)列踏堡,用于保存等待執(zhí)行的任務(wù)的阻塞隊(duì)列猎唁。可以選擇以下幾個(gè)阻塞隊(duì)列顷蟆。-
ArrayBlockingQueue
:是一個(gè)基于數(shù)組結(jié)構(gòu)的有界阻塞隊(duì)列诫隅,此隊(duì)列按FIFO
(先進(jìn)先出)原則對元素進(jìn)行排序腐魂。 -
LinkedBlockingQueue
:一個(gè)基于鏈表結(jié)構(gòu)的阻塞隊(duì)列,此隊(duì)列按FIFO
排序元素逐纬,吞吐量通常要高于ArrayBlockingQueue
蛔屹。靜態(tài)工廠方法Executors.newFixedThreadPool()
使用了這個(gè)隊(duì)列。 -
SynchronousQueue
:一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列豁生。每個(gè)插入操作必須等到另一個(gè)線程調(diào)用移除操作兔毒,否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于Linked-BlockingQueue
甸箱,靜態(tài)工廠方法Executors.newCachedThreadPool
使用了這個(gè)隊(duì)列育叁。 -
PriorityBlockingQueue
:一個(gè)具有優(yōu)先級(jí)的無限阻塞隊(duì)列。
-
-
handler
:飽和策略(又稱拒絕策略)芍殖。當(dāng)隊(duì)列和線程池都滿了豪嗽,說明線程池處于飽和狀態(tài),那么必須采取一種策略處理提交的新任務(wù)围小。這個(gè)策略默認(rèn)情況下是AbortPolicy
昵骤,表示無法處理新任務(wù)時(shí)拋出異常。在JDK 1.5
中 Java 線程池框架提供了以下4種策略肯适。-
AbortPolicy
:直接拋出異常。 -
CallerRunsPolicy
:只用調(diào)用者所在線程來運(yùn)行任務(wù)成榜。 -
DiscardOldestPolicy
:丟棄隊(duì)列里最近的一個(gè)任務(wù)框舔,并執(zhí)行當(dāng)前任務(wù)。 -
DiscardPolicy
:不處理赎婚,丟棄掉
-
歡迎關(guān)注公眾號(hào) 山間木匠 刘绣, 我是小春哥,從事 Java 后端開發(fā)挣输,會(huì)一點(diǎn)前端纬凤、通過持續(xù)輸出系列技術(shù)文章以文會(huì)友,如果本文能為您提供幫助撩嚼,歡迎大家關(guān)注停士、在看、 點(diǎn)贊完丽、分享支持恋技,我們下期再見!<br />