Java-并發(fā)編程知識點(diǎn)總結(jié)

目錄:

  1. 線程基礎(chǔ)
  2. 線程池
  3. 各種各樣的鎖
  4. 并發(fā)容器
  5. 原子類
  6. Java 內(nèi)存模型
  7. 線程協(xié)作
  8. 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 為釋放
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容