目錄:
- 線程基礎(chǔ)
- 線程池
- 各種各樣的鎖
- 并發(fā)容器
- 原子類
- Java 內(nèi)存模型
- 線程協(xié)作
- AQS 框架
一、線程基礎(chǔ)
1. 為什么繼承 runnable 接口比繼承 Thread 類的線程實(shí)現(xiàn)方式好?
- 可以把不同的執(zhí)行內(nèi)容解耦,全責(zé)分明
- 某些情況可以減少開銷,提高性能(比如可用線程池中已有的線程去執(zhí)行 runnable,而不用重新創(chuàng)建線程)
- 繼承 Thread 類的單繼承特性會限制代碼的擴(kuò)展性
2. 線程是如何在 6 種狀態(tài)之間轉(zhuǎn)化的?
- 線程的 6 種狀態(tài):New(新創(chuàng)建)、Runnable(可運(yùn)行)、Blocked(被阻塞)、Waiting(等待)、Timed_waiting(計(jì)時等待)、Terminated(被終止)
- 新創(chuàng)建線程處于 New 狀態(tài),調(diào)用 Thread#start 方法后進(jìn)入 Runnable 狀態(tài),Runnable 對應(yīng)操作系統(tǒng)的 Running 和 Ready 狀態(tài),代表可能正在被執(zhí)行或正在等待 CPU 分配資源
- 當(dāng)要進(jìn)入 synchronized 方法或代碼塊時卻沒搶到 monitor 鎖,會由 Runnable 狀態(tài)進(jìn)入 Blocked 狀態(tài),獲取到 monitor 鎖后會進(jìn)入 Runnable 狀態(tài)
- 執(zhí)行 Object#wait 或 LockSupport#park 會進(jìn)入 Waiting 狀態(tài);執(zhí)行帶 timeOut 參數(shù)的 Object#wait 或 LockSupport#park 會進(jìn)入 Timed_waiting 狀態(tài)
- 調(diào)用 LockSupport#unpark 、被中斷或超時時間到會由 Waiting/Timed_waiting 狀態(tài)進(jìn)入 Runnable 狀態(tài)
- 被 notify/notifyAll 喚醒,會由 Waiting/Timed_waiting 狀態(tài)進(jìn)入 Blocked 狀態(tài)
- run 方法執(zhí)行完或異常終止會進(jìn)入 Terminated 狀態(tài)
- 一個線程只會經(jīng)歷一次 New 和 Terminated 狀態(tài),中間狀態(tài)才可以相互轉(zhuǎn)換
3. 如何理解鎖池和等待池?
- 如果某對象的鎖已被一個線程占有,其他線程調(diào)用此對象的 sychronized 代碼塊時無法獲取到鎖,就會進(jìn)入此對象的鎖池,鎖池中的線程會競爭該對象的鎖
- 如果一個線程調(diào)用了 Object#wait 方法,此線程就會進(jìn)入此對象的等待池,等待池中的線程不會去競爭該對象的鎖
- 調(diào)用 notify 方法會隨機(jī)喚醒一個等待池中的線程,并移到鎖池中;調(diào)用 notifyAll 方法會喚醒等待池中所有的線程,并全移到鎖池中
4. 為什么 Object#wait 要寫在 while(condition) 循環(huán)中?
- 規(guī)避虛假喚醒導(dǎo)致的問題,虛假喚醒是指線程可能在未調(diào)用 notify/notifyAll、未被中斷和等待超時的情況下被意外喚醒,所以 wait 要寫在 while(condition) 循環(huán)中,保證在發(fā)生虛假喚醒時程序的正確性
5. 如何正確的中斷線程?
- 調(diào)用 Thread#interrupt 方法給線程發(fā)送中斷信號,線程中通過 Thread#isInterrupted 方法判斷是否被中斷,若被中斷則停止當(dāng)前執(zhí)行任務(wù)
- 線程中通過 Thread#sleep 或 BlockingQueue#put 等方法休眠時,若被中斷則會拋出 InterruptedException 異常并清除中斷標(biāo)記位,所以要捕獲處理此異常,或再調(diào)用 Thread#interrupt 標(biāo)記中斷使后續(xù)代碼能處理中斷
- 使用 volatile 標(biāo)記位變量中斷線程是錯誤的,因?yàn)椴荒苤袛?Thread#sleep 或 BlockingQueue#put 等方法進(jìn)入的休眠狀態(tài)
6. Object#wait 和 Thread#sleep 方法的異同?
- 相同點(diǎn):都可以讓線程阻塞;都可以響應(yīng)線程中斷
- 區(qū)別:wait 方法必須寫在 synchronized 代碼塊;wait 方法會主動釋放 monitor 鎖;sleep 方法必須傳入 timeout 參數(shù)
二、線程池
1. 使用線程池相比手動創(chuàng)建線程有什么優(yōu)點(diǎn)?
- 頻繁創(chuàng)建線程系統(tǒng)開銷大,而線程池可用一些固定的工作線程反復(fù)執(zhí)行任務(wù),避免頻繁創(chuàng)建線程
- 過多線程會占用過多內(nèi)存,而線程池可以控制線程的總數(shù)量,避免占用過多內(nèi)存資源
- 線程池可更方便的統(tǒng)籌管理任務(wù)執(zhí)行和線程,避免手動創(chuàng)建線程難管理、難統(tǒng)計(jì)的問題
2. 線程池各個參數(shù)的含義?
- corePoolSize 核心線程數(shù):常駐的工作線程,初始化時核心線程數(shù)默認(rèn)為 0,創(chuàng)建后不會被銷毀
- maximumPoolSize 最大線程數(shù):當(dāng) workQueue 存放滿時,線程池會進(jìn)一步創(chuàng)建線程,可創(chuàng)建的最多數(shù)量為 maximumPoolSize
- keepAliveTime/TimeUnit 空閑線程存活時間:當(dāng)大于 corePoolSize 部分的線程空閑超過存活時間后,會被回收
- threadFactory 用來創(chuàng)建線程的線程工廠:方便給線程自定義命名以及線程優(yōu)先級
- workQueue 存放任務(wù)的阻塞隊(duì)列:當(dāng)線程數(shù)超過 corePoolSize 后,會將任務(wù)存放到 workQueue 中等待執(zhí)行
- handler 任務(wù)被拒絕時的處理:當(dāng)線程池已 shutdown 關(guān)閉或線程數(shù)達(dá)到 maximumPoolSize 時新提交的任務(wù)會被拒絕
- 注意:當(dāng) workQueue 為無界隊(duì)列時, maximumPoolSize 參數(shù)其實(shí)不會被用到,是沒意義的
3. 線程池的四種拒絕策略?
- AbortPolicy:拋出 RejectedExecutionException 異常,可根據(jù)業(yè)務(wù)進(jìn)行重試等操作
- DiscardPolicy:直接丟棄新提交的任務(wù),不做其他反饋,有任務(wù)丟失風(fēng)險
- DiscardOldestPolicy:如果線程池未關(guān)閉,就丟棄隊(duì)列中存活時間最長的任務(wù),但不做其他反饋,有任務(wù)丟失風(fēng)險
- CallerRunsPolicy:如果線程池未關(guān)閉,就在提交任務(wù)的線程直接開始執(zhí)行任務(wù),任務(wù)不會被丟失,由于阻塞了提交任務(wù)的線程,相當(dāng)于提供了負(fù)反饋
4. 有哪 6 種常見的線程池?
- FixedThreadPool:固定線程數(shù)的線程池,核心線程數(shù)與最大線程數(shù)相同,任務(wù)存放隊(duì)列為無界阻塞隊(duì)列(LinkedBlockingQueue)
- CachedThreadPool:可緩存線程池,核心線程數(shù)為 0,最大線程數(shù)為 Integer.MAX_VALUE,任務(wù)存放隊(duì)列為中轉(zhuǎn)阻塞隊(duì)列(SynchronousQueue)
- SingleThreadExecutor:單工作線程線程池,核心線程數(shù)為 1,任務(wù)存放隊(duì)列為無界阻塞隊(duì)列(LinkedBlockingQueue)
- ScheduledThreadPool:定時或周期性任務(wù)線程池,任務(wù)存放隊(duì)列為無界優(yōu)先級阻塞隊(duì)列(DelayedWorkQueue)
- SingleThreadScheduledExecutor:定時或周期性任務(wù)單工作線程線程池,核心線程數(shù)為 1,任務(wù)存放隊(duì)列為無界優(yōu)先級阻塞隊(duì)列(DelayedWorkQueue)
- ForkJoinPool:適合執(zhí)行可以產(chǎn)生并行子任務(wù)的任務(wù),可方便的分裂(Fork)成子任務(wù)執(zhí)行并匯總(Join)結(jié)果,任務(wù)存放隊(duì)列為 WorkQueue,除了公用隊(duì)列外,每個線程還有一個獨(dú)立的隊(duì)列來存放任務(wù)
5. 線程池常用的阻塞隊(duì)列有哪些?
- LinkedBlockingQueue 無界阻塞隊(duì)列:任務(wù)隊(duì)列容量為 Integer.MAX_VALUE,永遠(yuǎn)不會放滿,所以對應(yīng)線程池只會創(chuàng)建核心線程數(shù)量的工作線程,而最大線程數(shù)參數(shù)對線程池來說沒有意義,因?yàn)椴⒉粫|發(fā)生成多于核心線程數(shù)的線程
- SynchronousQueue 中轉(zhuǎn)阻塞隊(duì)列:不存放任務(wù),一旦有任務(wù)被提交就直接轉(zhuǎn)發(fā)給線程或者創(chuàng)建新線程來執(zhí)行
- DelayedWorkQueue 無界優(yōu)先級阻塞隊(duì)列:內(nèi)部采用堆數(shù)據(jù)結(jié)構(gòu),按照延遲時間長短對任務(wù)進(jìn)行排序,ScheduledThreadPool 和 SingleThreadScheduledExecutor 選擇 DelayedWorkQueue,正是因?yàn)樗鼈儽旧硎腔跁r間執(zhí)行任務(wù)的,而延遲隊(duì)列正好可以把任務(wù)按時間進(jìn)行排序,方便任務(wù)的執(zhí)行
- ArrayBlockingQueue 有界隊(duì)列:任務(wù)隊(duì)列容量可配置,結(jié)合最大線程數(shù)與拒絕策略可有效的規(guī)避資源被耗盡的風(fēng)險
6. 為什么不建議使用常見的線程池?
- FixedThreadPool 和 SingleThreadExecutor 任務(wù)存放隊(duì)列為無界隊(duì)列(LinkedBlockingQueue),任務(wù)過多時會占用大量內(nèi)存并導(dǎo)致 OOM
- CachedThreadPool 雖然不存儲任務(wù),但線程數(shù)沒有上限,任務(wù)過多時會創(chuàng)建非常多的線程,導(dǎo)致超過線程數(shù)量上限或 OOM
- ScheduledThreadPool 和 SingleThreadScheduledExecutor 任務(wù)存放隊(duì)列為無界隊(duì)列(DelayedWorkQueue),任務(wù)過多時會占用大量內(nèi)存并導(dǎo)致 OOM
- 手動創(chuàng)建可以根據(jù)業(yè)務(wù)選擇合適的線程數(shù)量,制定拒絕策略,避免資源耗盡的風(fēng)險
7. 合適的線程數(shù)量是多少?
- CPU 密集型任務(wù)無需設(shè)置過多線程數(shù),因?yàn)榇祟惾蝿?wù)需占用大量 CPU 資源,設(shè)置過多線程數(shù)會導(dǎo)致多個線程都去搶占 CPU 資源,產(chǎn)生不必要的上下文切換,從而造成整體性能下降
- IO 密集型任務(wù)可設(shè)置較多線程數(shù),因?yàn)榇祟惾蝿?wù) IO 操作較耗時,但不會占用太多 CPU 資源,設(shè)置過少線程數(shù)會導(dǎo)致 CPU 資源空閑,導(dǎo)致 CPU 資源的浪費(fèi)
- 所以 CPU 耗時所占比例越高,就需要越少的線程;IO 耗時所占比例越高,就需要越多的線程
- 通用公式:線程數(shù) = CPU 核心數(shù) * (1 + IO 耗時/CPU 耗時)
- 例如 8 核機(jī)器執(zhí)行一個 CPU 耗時 5ms,DB 耗時 100ms 的任務(wù),線程數(shù) = 8*(1+100/5) = 168 個
- QPS(req pre second) 即一秒可執(zhí)行次數(shù),上例中 QPS = 168(1000/105) = 1600 。若 DB 最大 QPS 限制為 1000,則按比例減少線程數(shù)為 168(1000/1600) = 105 個
- 如果不同任務(wù)的 CPU 耗時和 IO 耗時各不相同,可對所有任務(wù)的 CPU 耗時和 IO 耗時求個平均值進(jìn)行計(jì)算;
8. 如何正確的關(guān)閉線程池?
- shutdown():調(diào)用后會在執(zhí)行完正在執(zhí)行任務(wù)和隊(duì)列中等待任務(wù)后才徹底關(guān)閉,并會根據(jù)拒絕策略拒絕后續(xù)新提交的任務(wù)
- shutdownNow():調(diào)用后會給正在執(zhí)行任務(wù)線程發(fā)送中斷信號,并將任務(wù)隊(duì)列中等待的任務(wù)轉(zhuǎn)移到一個 List 中返回,后續(xù)會根據(jù)拒絕策略拒絕新提交的任務(wù)
- isShutdown():判斷是否開始關(guān)閉線程池,即是否調(diào)用了 shutdown() 或 shutdownNow() 方法
- isTerminated():判斷線程池是否真正終止,即線程池已關(guān)閉且所有剩余的任務(wù)都執(zhí)行完了
- awaitTermination():阻塞一段時間等待線程池終止,返回 true 代表線程池真正終止否則為等待超時
9. 線程池線程復(fù)用的原理?
- 線程池將線程和任務(wù)解耦,一個線程可以從任務(wù)隊(duì)列中獲取多個任務(wù)執(zhí)行
- 關(guān)鍵類為 ThreadPoolExecutor 內(nèi)部的 Worker 類,對應(yīng)于一個線程,其內(nèi)部會從任務(wù)隊(duì)列中獲取多個任務(wù)執(zhí)行
三、各種各樣的鎖
1. 悲觀鎖/樂觀鎖
- 悲觀鎖指在操作同步資源前必須先拿到鎖;而樂觀鎖利用 CAS 理念,在不獨(dú)占資源的情況下對資源進(jìn)行修改
- 悲觀鎖適合用于并發(fā)寫入多、臨界區(qū)代碼復(fù)雜、競爭激烈等場景,這種場景下悲觀鎖可以避免大量的無用的反復(fù)嘗試等消耗
- 樂觀鎖適用于大部分是讀取,少部分是修改的場景,也適合雖然讀寫都很多,但是并發(fā)并不激烈的場景。在這些場景下,樂觀鎖不加鎖的特點(diǎn)能讓性能大幅提高
2. 可重入鎖/非可重入
- 可重入是如果指線程已經(jīng)持有鎖,則能在不釋放這把鎖的情況下,再次獲取這把鎖
- Java 中的 ReentrantLock 和 synchronized 都是可重入鎖
3. 共享鎖/獨(dú)占鎖
- 共享鎖指同一把鎖可以同時被多個線程獲取,而獨(dú)占鎖指一把鎖只能同時被一個線程獲取
- ReentrantReadWriteLock 的讀鎖就是共享鎖,可以同時被多個線程讀??;寫鎖則為獨(dú)占鎖,同時只能被一個線程寫
4. 自旋鎖/非自旋鎖
- 自旋是指拿不到鎖時不陷入阻塞,而是循環(huán)嘗試獲取鎖
- 自旋鎖適用于并發(fā)度不是特別高的場景,以及臨界區(qū)比較短小的情況,這樣我們可以利用避免線程切換來提高效率
- 如果臨界區(qū)很大,線程一旦拿到鎖,很久才會釋放的話,那就不合適用自旋鎖,因?yàn)樽孕龝恢闭加?CPU 卻無法拿到鎖,白白消耗資源
5. 公平鎖/非公平鎖
- 公平鎖是指各個線程公平平等,排隊(duì)獲取鎖時等待的時間越長就會優(yōu)先獲取到鎖,
- 非公平鎖是指線程可能存在插隊(duì)現(xiàn)象,比如一個阻塞等待中的線程 A 和新來的線程 B 同時競爭一把鎖時線程 B 會插隊(duì)先獲取到鎖
- 非公平鎖整體執(zhí)行速度為什么能更快:如上例,喚醒線程是需要耗時的,與其漫長的等待喚醒 A,不如直接先讓 B 插隊(duì)執(zhí)行,這樣可以跳過 B 阻塞、喚醒的狀態(tài)切換
- 非公平鎖的優(yōu)缺點(diǎn):整體執(zhí)行速度更快、吞吐量更大,但可能產(chǎn)生線程饑餓導(dǎo)致某個線程長時間得不到執(zhí)行
6. 可中斷鎖/不可中斷鎖
- 可中斷指等待獲取鎖時可被中斷從而取消等待;synchronized 是不可中斷鎖
7. 偏向鎖/輕量級鎖/重量級鎖
- 特指 synchronized 鎖的幾種狀態(tài)
- 鎖的升級路徑:無鎖->偏向鎖->輕量級鎖->重量級鎖
- 偏向鎖:當(dāng)一個線程第一次嘗試獲取某個對象的鎖時,僅記錄這個線程為偏向鎖的擁有者,后續(xù)獲取鎖時如果是同個線程,就可以直接獲取鎖,開銷很小,當(dāng)多線程發(fā)生實(shí)際競爭時會升級為輕量級鎖
- 輕量級鎖:線程會通過自旋的方式嘗試獲取鎖(自旋鎖),不會阻塞,開銷較小,當(dāng)鎖競爭時間較長時會膨脹為重量級鎖
- 重量級鎖:利用操作系統(tǒng)同步機(jī)制實(shí)現(xiàn),會讓線程進(jìn)入阻塞狀態(tài),開銷較大
8. JVM 對 synchronized 鎖做了哪些優(yōu)化?
- 鎖的升級:無鎖->偏向鎖->輕量級鎖->重量級鎖
- 鎖消除:虛擬機(jī)編譯時,對一些代碼上使用 synchronized 同步,但是被檢測到不可能存在共享數(shù)據(jù)競爭的鎖進(jìn)行削除
- 鎖粗化:把不間斷、高頻鎖的請求合并成一個請求,以降低短時間內(nèi)大量鎖請求、同步、釋放帶來的性能損耗
四、并發(fā)容器
1. Vector/HashTab
- 內(nèi)部使用 synchronized 方法級別的鎖保證線程安全,鎖的粒度比較大
- 在并發(fā)量高的時候很容易發(fā)生競爭,并發(fā)效率比較低
2. ConcurrentHashMap
- Java7 中基于普通的 HashMap 數(shù)組+鏈表結(jié)構(gòu),采用分段鎖的機(jī)制
- Java8 中基于數(shù)組+鏈表+紅黑樹結(jié)構(gòu),采用 CAS + synchronized 同步機(jī)制
- 紅黑樹相比鏈表可以提高查找效率,復(fù)雜度為 O(log(n))
- 為什么鏈表長度大于 8 時轉(zhuǎn)換為紅黑樹?如果 hashCode 分布離散良好、鏈表符合泊松分布,那鏈表長度為 8 的概率小于千萬分之一,紅黑樹更多的是一種保底策略,用來保證 hash 算法異常等極端情況下的查詢效率
- 為什么不采取僅數(shù)組+紅黑樹的結(jié)構(gòu)?紅黑樹節(jié)點(diǎn)相比鏈表占用內(nèi)存約大一倍,而鏈表較短時查找也很快,所以優(yōu)先采取鏈表結(jié)構(gòu)
3. CopyOnWriteArrayList
- 基于 CopyOnWrite 機(jī)制,寫入時會先創(chuàng)建一份副本,寫完副本后直接替換原內(nèi)容
- 優(yōu)點(diǎn):比讀寫鎖更近一步,只需寫寫互斥,讀取不用加鎖,對于讀多寫少的場景可以大幅提升性能
- 缺點(diǎn):寫入時存在創(chuàng)建副本開銷及副本所多占的內(nèi)存,讀寫不互斥可能會導(dǎo)致數(shù)據(jù)無法及時保持同步
五、原子類
1. 基本類型原子類
- 包括 AtomicInteger、AtomicLong、AtomicBoolean
- 提供了基本類型的 getAndSet、compareAndSet 等原子操作
- 底層基于 Unsafe#compareAndSwapInt、Unsafe#compareAndSwapLong 等實(shí)現(xiàn)
2. 數(shù)組類型原子類
- 包括 AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
- 數(shù)組里的元素都可以保證其原子性,相當(dāng)于把基本類型原子類聚合起來,組合成一個數(shù)組
3. 引用類型原子類
- 包括 AtomicReference、AtomicStampedReference、AtomicMarkableReference
- 用于讓一個對象保證原子性,底層基于 Unsafe#compareAndSwapObject 等實(shí)現(xiàn)
- AtomicStampedReference 是對 AtomicReference 的升級,在此基礎(chǔ)上加了時間戳,用于解決 CAS 的 ABA 問題
4. 升級類型原子類
- 包括 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
- 對于非原子的基本或引用類型,在不改變其原類型的前提下,提供原子更新的能力
- 適用于由于歷史原因改動成本太大或極少情況用到原子性的場景
5. 累加器
- 包括 LongAdder、DoubleAdder
- 相比于基本類型原子類,累加器沒有 compareAndSwap、addAndGet 等方法,功能較少
- 設(shè)計(jì)原理:將 value 分散到一個數(shù)組中,不同線程只針對自己命中的槽位進(jìn)行修改,減小高并發(fā)場景的線程競爭概率,類似于 ConcurrentHashMap 的分段鎖思想
- 可解決高并發(fā)場景 AtomicLong 的過多自旋問題
6. 積累器
- 包括 LongAccumulator、DoubleAccumulator
- 是 LongAdder、DoubleAdder 的功能增強(qiáng)版,提供了自定義的函數(shù)操作
7. 原子類與鎖
- 都是為了保證并發(fā)場景下線程安全
- 原子類粒度更細(xì),競爭范圍為變量級別
- 原子類效率更高,底層采取 CAS 操作,不會阻塞線程
- 原子類不適用于高并發(fā)場景,因?yàn)闊o限循環(huán)的 CAS 操作會占用 CPU
8. 原子類與 volatile
- volatile 具有可見性和有序性,但不具備原子性
- volatile 修飾 boolean 類型通常保證線程安全,因?yàn)橘x值操作具有原子性
- volatile 修飾 int 類型通常無法保證線程安全,因?yàn)?int 類型的計(jì)算操作需要讀取、修改、賦值回去,不是原子操作,這時需要使用原子類
六、Java 內(nèi)存模型
1. 內(nèi)存結(jié)構(gòu)與內(nèi)存模型
- 內(nèi)存結(jié)構(gòu)描述了 JVM 運(yùn)行時內(nèi)存區(qū)域結(jié)構(gòu),包括:堆、方法區(qū)、虛擬機(jī)棧、本地方法棧、程序計(jì)數(shù)器、運(yùn)行時常量池
- 內(nèi)存模型(JMM)是和多線程相關(guān)的一組規(guī)范,與 Java 并發(fā)編程有關(guān)
2. 主內(nèi)存和工作內(nèi)存
- CPU 有多級緩存,會存在數(shù)據(jù)不同步的情況,JMM 屏蔽了 CPU 緩存的底層細(xì)節(jié),抽象為主內(nèi)存和工作內(nèi)存
- 工作內(nèi)存中存在一份主內(nèi)存數(shù)據(jù)的副本,每個線程只能接觸工作內(nèi)存,無法直接操作主內(nèi)存
3. 內(nèi)存可見性
- 指一個線程修改了工作內(nèi)存的值后,其他線程能正確感知到最新的值
- 滿足于 happens-before 關(guān)系的原則具備可見行,比如單線程、volatile、鎖同步等規(guī)則
4. 指令重排序
- 編譯器、JVM 或者 CPU 都有可能出于優(yōu)化等目的,對于實(shí)際指令執(zhí)行的順序進(jìn)行調(diào)整,這就是重排序
- volatile 具備禁止重排序的特性
- 單例模式的雙重檢查模式需要添加 volatile 修飾,規(guī)避指令重排序?qū)е碌膶ο笠门袛嗖粸?null,但對象仍未初始化完的問題
七、線程協(xié)作
1. Semaphore
- 通過控制許可證的發(fā)放和歸還實(shí)現(xiàn)統(tǒng)一時刻可執(zhí)行某任務(wù)的最大線程數(shù)
- 信號量可以被 FixedThreadPool 代替嗎?不能,信號量具有可跨線程、跨線程池的特性,相比 FixedThreadPool 更靈活,更適合于限制并發(fā)訪問的線程數(shù)
2. CountDownLatch
- 用于并發(fā)流程控制,等到一個設(shè)定的數(shù)值達(dá)到之后,才能開始執(zhí)行
- 不可重用,若已完成倒數(shù),則不能再重置使用
3. CyclicBarrier
- 與 CountDownLatch 類似,都能阻塞一個或一組線程,直到某個預(yù)設(shè)的條件達(dá)成,再統(tǒng)一出發(fā)
- CountDownLatch 作用于一個線程,CountDownLatch 作用于事件
- 可重用,若已達(dá)成條件,可重置繼續(xù)使用
- 可定義條件達(dá)成后的自定義執(zhí)行動作
八、AQS 框架
1. AQS 及存在的意義?
- AQS 是一個用于構(gòu)建鎖、同步器等線程協(xié)作工具類的框架,即 AbstractQueuedSynchronizer 類
- ReentrantLock、Semaphore、CountDownLatch 等工具類的工作都是類似的,AQS 就是這些類似工作提取出來的公共部分,比如閥門功能、調(diào)度線程等
- AQS 可以極大的減少上層工具類的開發(fā)工作量,也可以避免上層處理不當(dāng)導(dǎo)致的線程安全問題
2. AQS 內(nèi)部的關(guān)鍵原理
- state 值:AQS 中具有一個 int 類型的 state 變量,在不同工具類中代表不同的含義,比如在 Semaphore 中代表剩余許可證的數(shù)量;在 CountDownLatch 中代表需要倒數(shù)的數(shù)量;在 ReentrantLock 中代表鎖的占有情況,0 代表沒被占有,1 代表被占有,大于 1 代表同個線程重入了
- FIFO 隊(duì)列:用于存儲、管理等待的線程
- 獲取、釋放鎖:需工具類自行實(shí)現(xiàn),比如 Semaphore#acquire、ReentrantLock#lock 為獲?。?Semaphore#release、ReentrantLock#unlock 為釋放