主頁 > 後端開發 > Java多執行緒連環50問(八股文背誦版)

Java多執行緒連環50問(八股文背誦版)

2021-09-25 16:53:40 後端開發

本文包含了多執行緒基礎、synchronized、ThreadLocal、ReentrantLock、volatile 、執行緒池、AQS和原子類的多執行緒常見面試題,
共50道,接近2w字,收藏點贊再看吧~

本文目錄:

我總結的500道大廠高頻面試題pdf:點擊此處 領取即可

基礎

1. 執行緒和行程有什么區別?

執行緒具有許多傳統行程所具有的特征,故又稱為輕型行程(Light—Weight Process)或行程元;而把傳統的行程稱為重型行程(Heavy—Weight Process),它相當于只有一個執行緒的任務,在引入了執行緒的作業系統中,通常一個行程都有若干個執行緒,至少包含一個執行緒,

根本區別:行程是作業系統資源分配的基本單位,而執行緒是處理器任務調度和執行的基本單位

資源開銷:每個行程都有獨立的代碼和資料空間(程式背景關系),程式之間的切換會有較大的開銷;執行緒可以看做輕量級的行程,同一類執行緒共享代碼和資料空間,每個執行緒都有自己獨立的運行堆疊和程式計數器(PC),執行緒之間切換的開銷小,

包含關系:如果一個行程內有多個執行緒,則執行程序不是一條線的,而是多條線(執行緒)共同完成的;執行緒是行程的一部分,所以執行緒也被稱為輕權行程或者輕量級行程,

記憶體分配:同一行程的執行緒共享本行程的地址空間和資源,而行程之間的地址空間和資源是相互獨立的

影響關系:一個行程崩潰后,在保護模式下不會對其他行程產生影響,但是一個執行緒崩潰整個行程都死掉,所以多行程要比多執行緒健壯,

執行程序:每個獨立的行程有程式運行的入口. 順序執行序列和程式出口,但是執行緒不能獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制,兩者均可并發執行

2. 創建執行緒的三種方式的對比?

1)采用實作Runnable. Callable介面的方式創建多執行緒,

優勢是

執行緒類只是實作了Runnable介面或Callable介面,還可以繼承其他類,

在這種方式下,多個執行緒可以共享同一個target物件,所以非常適合多個相同執行緒來處理同一份資源的情況,從而可以將CPU. 代碼和資料分開,形成清晰的模型,較好地體現了面向物件的思想,

劣勢是:

編程稍微復雜,如果要訪問當前執行緒,則必須使用Thread.currentThread()方法,

2)使用繼承Thread類的方式創建多執行緒

優勢是:

撰寫簡單,如果需要訪問當前執行緒,則無需使用Thread.currentThread()方法,直接使用this即可獲得當前執行緒,

劣勢是:

執行緒類已經繼承了Thread類,所以不能再繼承其他父類,

3)Runnable和Callable的區別

  • Callable規定(重寫)的方法是call(),Runnable規定(重寫)的方法是run(),

  • Callable的任務執行后可回傳值,而Runnable的任務是不能回傳值的,

  • Call方法可以拋出例外,run方法不可以,

  • 運行Callable任務可以拿到一個Future物件,表示異步計算的結果,它提供了檢查計算是否完成的方法,以等待計算的完成,并檢索計算的結果,通過Future物件可以了解任務執行情況,可取消任務的執行,還可獲取執行結果,

3. 為什么要使用多執行緒呢?

  • 從計算機底層來說: 執行緒可以比作是輕量級的行程,是程式執行的最小單位,執行緒間的切換和調度的成本遠遠小于行程,另外,多核 CPU 時代意味著多個執行緒可以同時運行,這減少了執行緒背景關系切換的開銷,

  • 從當代互聯網發展趨勢來說: 現在的系統動不動就要求百萬級甚至千萬級的并發量,而多執行緒并發編程正是開發高并發系統的基礎,利用好多執行緒機制可以大大提高系統整體的并發能力以及性能,

從計算機底層來說:

  • 單核時代: 在單核時代多執行緒主要是為了提高 CPU 和 IO 設備的綜合利用率,舉個例子:當只有一個執行緒的時候會導致 CPU 計算時,IO 設備空閑;進行 IO 操作時,CPU 空閑,我們可以簡單地說這兩者的利用率目前都是 50%左右,但是當有兩個執行緒的時候就不一樣了,當一個執行緒執行 CPU 計算時,另外一個執行緒可以進行 IO 操作,這樣兩個的利用率就可以在理想情況下達到 100%了,

  • 多核時代:多核時代多執行緒主要是為了提高 CPU 利用率,舉個例子:假如我們要計算一個復雜的任務,我們只用一個執行緒的話,CPU 只會一個 CPU 核心被利用到,而創建多個執行緒就可以讓多個 CPU 核心被利用到,這樣就提高了 CPU 的利用率,

4. 執行緒的狀態流轉

執行緒的生命周期及五種基本狀態:

Java執行緒具有五中基本狀態

1)新建狀態(New):當執行緒物件對創建后,即進入了新建狀態,如:Thread t = new MyThread();

2)就緒狀態(Runnable):當呼叫執行緒物件的start()方法(t.start();),執行緒即進入就緒狀態,處于就緒狀態的執行緒,只是說明此執行緒已經做好了準備,隨時等待CPU調度執行,并不是說執行了t.start()此執行緒立即就會執行;

3)運行狀態(Running):當CPU開始調度處于就緒狀態的執行緒時,此時執行緒才得以真正執行,即進入到運行狀態,注:就 緒狀態是進入到運行狀態的唯一入口,也就是說,執行緒要想進入運行狀態執行,首先必須處于就緒狀態中;

4)阻塞狀態(Blocked):處于運行狀態中的執行緒由于某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU呼叫以進入到運行狀態,根據阻塞產生的原因不同,阻塞狀態又可以分為三種:

1.等待阻塞:運行狀態中的執行緒執行wait()方法,使本執行緒進入到等待阻塞狀態;

2.同步阻塞 — 執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所占用),它會進入同步阻塞狀態;

3.其他阻塞 — 通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態,當sleep()狀態超時. join()等待執行緒終止或者超時. 或者I/O處理完畢時,執行緒重新轉入就緒狀態,

5)死亡狀態(Dead):執行緒執行完了或者因例外退出了run()方法,該執行緒結束生命周期,

5. 什么是執行緒死鎖?如何避免死鎖?

死鎖

  • 多個執行緒同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放,由于執行緒被無限期地阻塞,因此程式不可能正常終止,

死鎖必須具備以下四個條件:

  • 互斥條件:該資源任意一個時刻只由一個執行緒占用,

  • 請求與保持條件:一個行程因請求資源而阻塞時,對已獲得的資源保持不放,

  • 不剝奪條件:執行緒已獲得的資源在末使用完之前不能被其他執行緒強行剝奪,只有自己使用完畢后才釋放資源,

  • 回圈等待條件:若干行程之間形成一種頭尾相接的回圈等待資源關系,

如何避免執行緒死鎖?

只要破壞產生死鎖的四個條件中的其中一個就可以了

  • 破壞互斥條件 這個條件我們沒有辦法破壞,因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥訪問)

  • 破壞請求與保持條件 一次性申請所有的資源,

  • 破壞不剝奪條件 占用部分資源的執行緒進一步申請其他資源時,如果申請不到,可以主動釋放它占有的資源,

  • 破壞回圈等待條件 靠按序申請資源來預防,按某一順序申請資源,釋放資源則反序釋放,破壞回圈等待條件,

  • 鎖 排序 法:(必須回答出來的點) 指定獲取鎖的順序,比如某個執行緒只有獲得A鎖和B鎖,才能對某資源進行操作,在多執行緒條件下,如何避免死鎖? 通過指定鎖的獲取順序,比如規定,只有獲得A鎖的執行緒才有資格獲取B鎖,按順序獲取鎖就可以避免死鎖,這通常被認為是解決死鎖很好的一種方法,

  • 使用顯式鎖中的ReentrantLock.try(long,TimeUnit)來申請鎖

6. 常見的對比

Runnable VS Callable

  • Callable僅在 Java 1.5 中引入,目的就是為了來處理Runnable不支持的用例,Callable 介面可以回傳結果或拋出檢查例外

  • Runnable 介面不會回傳結果或拋出檢查例外,

  • 如果任務不需要回傳結果或拋出例外推薦使用 Runnable介面,這樣代碼看起來會更加簡潔

  • 工具類 Executors 可以實作 Runnable 物件和 Callable 物件之間的相互轉換,(Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule))

shutdown() VS shutdownNow()

  • shutdown() :關閉執行緒池,執行緒池的狀態變為 SHUTDOWN,執行緒池不再接受新任務了,但是佇列里的任務得執行完畢,

  • shutdownNow() :關閉執行緒池,執行緒的狀態變為 STOP,執行緒池會終止當前正在運行的任務,并停止處理排隊的任務并回傳正在等待執行的 List, shutdownNow的原理是遍歷執行緒池中的作業執行緒,然后逐個呼叫執行緒的interrupt方法來中斷執行緒,所以無法回應中斷的任務可能永遠無法終

isTerminated() VS isShutdown()

  • isShutDown 當呼叫 shutdown() 方法后回傳為 true,

  • isTerminated 當呼叫 shutdown() 方法后,并且所有提交的任務完成后回傳為 true

7. sleep() 方法和 wait() 方法區別和共同點?

區別

  • sleep方法:是Thread類的靜態方法,當前執行緒將睡眠n毫秒,執行緒進入阻塞狀態,當睡眠時間到了,會解除阻塞,進入可運行狀態,等待CPU的到來,睡眠不釋放鎖(如果有的話),

  • wait方法:是Object的方法,必須與synchronized關鍵字一起使用,執行緒進入阻塞狀態,當notify或者notifyall被呼叫后,會解除阻塞,但是,只有重新占用互斥鎖之后才會進入可運行狀態,睡眠時,會釋放互斥鎖,

  • sleep 方法沒有釋放鎖,而 wait 方法釋放了鎖 ,

  • sleep 通常被用于暫停執行Wait 通常被用于執行緒間互動/通信

  • sleep() 方法執行完成后,執行緒會自動蘇醒,或者可以使用 wait(long timeout)超時后執行緒會自動蘇醒,wait() 方法被呼叫后,執行緒不會自動蘇醒,需要別的執行緒呼叫同一個物件上的 notify() 或者 notifyAll() 方法

相同

  • 兩者都可以暫停執行緒的執行,

8.為什么我們呼叫 start() 方法時會執行 run() 方法,為什么我們不能直接呼叫 run() 方法


  • new 一個 Thread,執行緒進入了新建狀態; 呼叫start() 會執行執行緒的相應準備作業,然后自動執行 run() 方法的內容,(呼叫 start() 方***啟動一個執行緒并使執行緒進入了就緒狀態,當分配到時間片后就可以開始運行了,)這是真正的多執行緒作業,

  • 直接執行 run() 方把 run 方法當成一個 main 執行緒下的普通方法去執行,并不會在某個執行緒中執行它,所以這并不是多執行緒作業, *呼叫 start 方法方可啟動執行緒并使執行緒進入就緒狀態,而 run 方法只是 thread 的一個普通方法呼叫,還是在主執行緒里執行,

9. Thread類中的yield方法有什么作用?

Yield方法可以暫停當前正在執行的執行緒物件,讓其它有相同優先級的執行緒執行,它是一個靜態方法而且只保證當前執行緒放棄CPU占用而不能保證使其它執行緒一定能占用CPU,執行yield()的執行緒有可能在進入到暫停狀態后馬上又被執行,

10. 談談volatile的使用及其原理

volatile的兩層語意

  1. volatile保證變數對所有執行緒的可見性:當volatile變數被修改,新值對所有執行緒會立即更新,或者理解為多執行緒環境下使用volatile修飾的變數的值一定是最新的,

  2. jdk1.5以后volatile完全避免了指令重排優化,實作了有序性,

volatile的原理:

獲取JIT(即時Java編譯器,把位元組碼解釋為機器語言發送給處理器)的匯編代碼,發現volatile多加了lock addl指令,這個操作相當于一個記憶體屏障,使得lock指令后的指令不能重排序到記憶體屏障前的位置,這也是為什么JDK1.5以后可以使用雙鎖檢測實作單例模式,

lock前綴的另一層意義是使得本執行緒作業記憶體中的volatile變數值立即寫入到主記憶體中,并且使得其他執行緒共享的該volatile變數無效化,這樣其他執行緒必須重新從主記憶體中讀取變數值,

11. 如何創建執行緒實體并運行?

Thread 類本質上是實作 Runnable 介面的一個實體,代表一個執行緒的實體,創建執行緒實體一般有兩種方法:

  1. 創建 Thread 的子類并重寫 run()

1

public class MyThread extends Thread { <a href="/profile/992988" data-card-uid="992988" class="" target="_blank" from-niu="default" data-card-index="3">@Override public void run(){ System.out.println("MyThread running"); } }</a>

run() 方在呼叫 start() 方法后被執行,而且一旦執行緒啟動后 start() 方法后就會立即回傳,而不是等到 run() 方法執行完畢后再回傳,

1

MyThread myThread = new MyThread(); myThread.start();

  1. 實作 Runnable 介面

1

public class MyRunnable implements Runnable{ <a href="/profile/992988" data-card-uid="992988" class="" target="_blank" from-niu="default" data-card-index="4">@Override public void run(){ System.out.println("MyRunnable running"); } }</a>

在新建類時實作 Runnable 介面,然后在 Thread 類的建構式中傳入 MyRunnable 的實體物件,最后執行 start() 方法即可;

1

Thread thread = new Thread(new MyRunnable()); thread.start();

12. 執行緒阻塞的三種情況

當執行緒因為某種原因放棄 CPU 使用權后,即讓出了 CPU 時間片,暫時就會停止運行,知道執行緒進入可運行狀態(Runnable),才有機會再次獲得 CPU 時間片轉入 RUNNING 狀態,一般來講,阻塞的情況可以分為如下三種:

  1. 等待阻塞(Object.wait -> 等待佇列)

RUNNING 狀態的執行緒執行 Object.wait() 方法后,JVM 會將執行緒放入等待序列(waitting queue);

  1. 同步阻塞(lock -> 鎖池)

RUNNING 狀態的執行緒在獲取物件的同步鎖時,若該 同步鎖被其他執行緒占用,則 JVM 將該執行緒放入鎖池(lock pool)中

  1. 其他阻塞(sleep/join)

RUNNING 狀態的執行緒執行 Thread.sleep(long ms) 或 Thread.join() 方法,或發出 I/O 請求時,JVM 會將該執行緒置為阻塞狀態,當 sleep() 狀態超時,join() 等待執行緒終止或超時. 或者 I/O 處理完畢時,執行緒重新轉入可運行狀態(RUNNABLE);

13. 執行緒死亡的三種方式

  1. 正常結束

run() 或者 call() 方法執行完成后,執行緒正常結束;

  1. 例外結束

執行緒拋出一個未捕獲的 Exception 或 Error,導致執行緒例外結束;

  1. 呼叫 stop()

直接呼叫執行緒的 stop() 方法來結束該執行緒,但是一般不推薦使用該種方式,因為該方法通常容易導致死鎖

14. 為什么我們呼叫start()方法時會執行run()方法,為什么我們不能直接呼叫run()方法?

JVM執行start方***另起一條執行緒執行thread的run方法,這才起到多執行緒的效果~

如果直接呼叫Thread的run()方法,其方法還是運行在主執行緒中,沒有起到多執行緒效果,

15. 守護執行緒是什么?

守護執行緒是運行在后臺的一種特殊行程,它獨立于控制終端并且周期性地執行某種任務或等待處理某些發生的事件,在 Java 中垃圾回收執行緒就是特殊的守護執行緒,

16. 了解Fork/Join框架嗎?

Fork/Join框架是Java7提供的一個用于并行執行任務的框架,是一個把大任務分割成若干個小任務,最侄訓總每個小任務結果后得到大任務結果的框架,

Fork/Join框架需要理解兩個點,「分而治之」「作業竊取演算法

「分而治之」

以上Fork/Join框架的定義,就是分而治之思想的體現啦

img

「作業竊取演算法

把大任務拆分成小任務,放到不同佇列執行,交由不同的執行緒分別執行時,有的執行緒優先把自己負責的任務執行完了,其他執行緒還在慢慢悠悠處理自己的任務,這時候為了充分提高效率,就需要作業***演算法啦~

img

作業*演算法就是,「某個執行緒從其他佇列中竊取任務進行執行的程序」,一般就是指做得快的執行緒(*執行緒)搶慢的執行緒的任務來做,同時為了減少鎖競爭,通常使用雙端佇列,即快執行緒和慢執行緒各在一端,

17. CAS了解嗎?

  • CAS:全稱 Compare and swap,即比較并交換,它是一條 CPU 同步原語,是一種硬體對并發的支持,針對多處理器操作而設計的一種特殊指令,用于管理對共享資料的并發訪問,

  • CAS 是一種無鎖的非阻塞演算法的實作,

  • CAS 包含了 3 個運算元:

    • 需要讀寫的記憶體值 V

    • 舊的預期值 A

    • 要修改的更新值 B

  • 當且僅當 V 的值等于 A 時,CAS 通過原子方式用新值 B 來更新 V 的 值,否則不會執行任何操作(他的功能是判斷記憶體某個位置的值是否為預期值,如果是則更改為新的值,這個程序是原子的,)

CAS 并發原語體現在 Java 語言中的 sum.misc.Unsafe 類中的各個方法,呼叫 Unsafe 類中的 CAS 方法, JVM 會幫助我們實作出 CAS 匯編指令,這是一種完全依賴于硬體的功能,通過它實作了原子操作,再次強調,由于 CAS是一種系統原語,原語屬于作業系統用于范疇,是由若干條指令組成的,用于完成某個功能的一個程序,并且原語的執行必須是連續的在執行程序中不允許被中斷,CAS 是一條 CPU 的原子指令,不會造成資料不一致問題,

18. CAS有什么缺陷?

img

1. ABA 問題

并發環境下,假設初始條件是A,去修改資料時,發現是A就會執行修改,但是看到的雖然是A,中間可能發生了A變B,B又變回A的情況,此時A已經非彼A,資料即使成功修改,也可能有問題,

可以通過AtomicStampedReference解決ABA問題,它,一個帶有標記的原子參考類,通過控制變數值的版本來保證CAS的正確性,

2. 回圈時間長開銷

自旋CAS,如果一直回圈執行,一直不成功,會給CPU帶來非常大的執行開銷,

很多時候,CAS思想體現,是有個自旋次數的,就是為了避開這個耗時問題~

3. 只能保證一個變數的原子操作,

CAS 保證的是對一個變數執行操作的原子性,如果對多個變數操作時,CAS 目前無法直接保證操作的原子性的,

可以通過這兩個方式解決這個問題

  • 使用互斥鎖來保證原子性;

  • 將多個變數封裝成物件,通過AtomicReference來保證原子性,

19. synchronized 和 volatile 的區別是什么?

volatile 解決的是記憶體可見性問題,會使得所有對 volatile 變數的讀寫都直接寫入主存,即 保證了變數的可見性

synchronized 解決的事執行控制的問題,它會阻止其他執行緒獲取當前物件的監控鎖,這樣一來就讓當前物件中被 synchronized 關鍵字保護的代碼塊無法被其他執行緒訪問,也就是無法并發執行,而且,synchronized 還會創建一個 記憶體屏障,記憶體屏障指令保證了所有 CPU 操作結果都會直接刷到主存中,從而 保證操作的記憶體可見性,同時也使得這個鎖的執行緒的所有操作都 happens-before 于隨后獲得這個鎖的執行緒的操作,

兩者的區別主要有如下:

  1. volatile 本質是在告訴 JVM 當前變數在暫存器(作業記憶體)中的值是不確定的,需要從主存中讀取; synchronized 則是鎖定當前變數,只有當前執行緒可以訪問該變數,其他執行緒被阻塞住,

  2. volatile 僅能使用在變數級別;synchronized 則可以使用在 變數. 方法. 和類級別的

  3. volatile 僅能實作變數的修改可見性,不能保證原子性;而synchronized 則可以 保證變數的修改可見性和原子性

  4. volatile 不會造成執行緒的阻塞;synchronized 可能會造成執行緒的阻塞

  5. volatile 標記的變數不會被編譯器優化;synchronized 標記的變數可以被編譯器優化,

20. synchronized 和 Lock 有什么區別?

  • synchronized 可以給類. 方法. 代碼塊加鎖;而 lock 只能給代碼塊加鎖,

  • synchronized 不需要手動獲取鎖和釋放鎖,使用簡單,發生例外會自動釋放鎖,不會造成死鎖;而 lock 需要自己加鎖和釋放鎖,如果使用不當沒有 unLock()去釋放鎖就會造成死鎖,

  • 通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到,

21. synchronized 和 ReentrantLock 區別是什么?

1.兩者都是可重入鎖

可重入鎖:重入鎖,也叫做遞回鎖,可重入鎖指的是在一個執行緒中可以多次獲取同一把鎖,比如: 一個執行緒在執行一個帶鎖的方法,該方法中又呼叫了另一個需要相同鎖的方法,則該執行緒可以直接執行呼叫的方法,而無需重新獲得鎖, 兩者都是同一個執行緒每進入一次,鎖的計數器都自增1,所以要等到鎖的計數器下降為0時才能釋放鎖,

2.synchronized 依賴于 JVM 而 ReentrantLock 依賴于 API

  • synchronized 是依賴于 JVM 實作的,前面我們也講到了 虛擬機團隊在 JDK1.6 為 synchronized 關鍵字進行了很多優化,但是這些優化都是在虛擬機層面實作的

  • ReentrantLock 是 JDK 層面實作的(也就是 API 層面,需要 lock() 和 unlock() 方法配合 try/finally 陳述句塊來完成)

3.ReentrantLock 比 synchronized 增加了一些高級功能

相比synchronized,ReentrantLock增加了一些高級功能,主要來說主要有三點:①等待可中斷;②可實作公平鎖;③可實作選擇性通知(鎖可以系結多個條件)

  • 等待可中斷.通過lock.lockInterruptibly()來實作這個機制,也就是說正在等待的執行緒可以選擇放棄等待,改為處理其他事情,

  • ReentrantLock可以指定是公平鎖還是非公平鎖,而synchronized只能是非公平鎖,所謂的公平鎖就是先等待的執行緒先獲得鎖, ReentrantLock默認情況是非公平的,可以通過 ReentrantLock類的ReentrantLock(boolean fair)構造方法來制定是否是公平的,

  • ReentrantLock類執行緒物件可以注冊在指定的Condition中,從而可以有選擇性的進行執行緒通知,在調度執行緒上更加靈活, 在使用notify()/notifyAll()方法進行通知時,被通知的執行緒是由 JVM 選擇的,用ReentrantLock類結合Condition實體可以實作“選擇性通知”

4.使用選擇

  • 除非需要使用 ReentrantLock 的高級功能,否則優先使用 synchronized,

  • synchronized 是 JVM 實作的一種鎖機制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持,并且使用 synchronized 不用擔心沒有釋放鎖而導致死鎖問題,因為 JVM 會確保鎖的釋放

22. synchronized的用法有哪些?

  • 修飾普通方法:作用于當前物件實體,進入同步代碼前要獲得當前物件實體的鎖

  • 修飾靜態方法:作用于當前類,進入同步代碼前要獲得當前類物件的鎖,synchronized 關鍵字加到 static 靜態方法和 synchronized(class)代碼塊上都是是給 Class 類上鎖

  • 修飾代碼塊:指定加鎖物件,對給定物件加鎖,進入同步代碼庫前要獲得給定物件的鎖

特別注意:

①如果一個執行緒A呼叫一個實體物件的非靜態 synchronized 方法,而執行緒B需要呼叫這個實體物件所屬類的靜態 synchronized 方法,是允許的,不會發生互斥現象,因為訪問靜態 synchronized 方法占用的鎖是當前類的鎖

②盡量不要使用 synchronized(String s) ,因為JVM中,字串常量池具有緩沖功能

23. Synchronized的作用有哪些?

  1. 原子性:確保執行緒互斥的訪問同步代碼;

  2. 可見性:保證共享變數的修改能夠及時可見,其實是通過Java記憶體模型中的 “對一個變數unlock操作之前,必須要同步到主記憶體中;如果對一個變數進行lock操作,則將會清空作業記憶體中此變數的值,在執行引擎使用此變數前,需要重新從主記憶體中load操作或assign操作初始化變數值” 來保證的;

  3. 有序性:有效解決重 排序 問題,即 “一個unlock操作先行發生(happen-before)于后面對同一個鎖的lock操作”,

24. 說一下 synchronized 底層實作原理?

synchronized 同步代碼塊的實作是通過 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束位置,當執行 monitorenter 指令時,執行緒試圖獲取鎖也就是獲取 monitor(monitor物件存在于每個Java物件的物件頭中,synchronized 鎖便是通過這種方式獲取鎖的,也是為什么Java中任意物件可以作為鎖的原因) 的持有權,

其內部包含一個計數器,當計數器為0則可以成功獲取,獲取后將鎖計數器設為1也就是加1,相應的在執行 monitorexit 指令后,將鎖計數器設為0,表明鎖被釋放,如果獲取物件鎖失敗,那當前執行緒就要阻塞等待,直到鎖被另外一個執行緒釋放為止

synchronized 修飾的方法并沒有 monitorenter 指令和 monitorexit 指令,取得代之的確實是 ACC_SYNCHRONIZED 標識,該標識指明了該方法是一個同步方法,JVM 通過該 ACC_SYNCHRONIZED 訪問標志來辨別一個方法是否宣告為同步方法,從而執行相應的同步呼叫,

25. 多執行緒中 synchronized 鎖升級的原理是什么?

synchronized 鎖升級原理:在鎖物件的物件頭里面有一個 threadid 欄位,在第一次訪問的時候 threadid 為空,jvm 讓其持有偏向鎖,并將 threadid 設定為其執行緒 id,再次進入的時候會先判斷 threadid 是否與其執行緒 id 一致,如果一致則可以直接使用此物件,如果不一致,則升級偏向鎖為輕量級鎖,通過自旋回圈一定次數來獲取鎖,執行一定次數之后,如果還沒有正常獲取到要使用的物件,此時就會把鎖從輕量級升級為重量級鎖,此程序就構成了 synchronized 鎖的升級,

鎖的升級的目的:鎖升級是為了減低了鎖帶來的性能消耗,在 Java 6 之后優化 synchronized 的實作方式,使用了偏向鎖升級為輕量級鎖再升級到重量級鎖的方式,從而減低了鎖帶來的性能消耗,

26. synchronized 為什么是非公平鎖?非公平體現在哪些地方?

synchronized 的非公平其實在 原始碼 中應該有不少地方,因為設計者就沒按公平鎖來設計,核心有以下幾個點:

1)當持有鎖的執行緒釋放鎖時,該執行緒會執行以下兩個重要操作:

  1. 先將鎖的持有者 owner 屬性賦值為 null

  2. 喚醒等待 鏈表中的一個執行緒(假定繼承者),

在1和2之間,如果有其他執行緒剛好在嘗試獲取鎖(例如自旋),則可以馬上獲取到鎖,

2)當執行緒嘗試獲取鎖失敗,進入阻塞時,放入鏈表的順序,和最終被喚醒的順序是不一致的,也就是說你先進入鏈表,不代表你就會先被喚醒,

27. JVM對synchronized的優化有哪些?

從最近幾個jdk版本中可以看出,Java的開發團隊一直在對synchronized優化,其中最大的一次優化就是在jdk6的時候,新增了兩個鎖狀態,通過鎖消除、鎖粗化、自旋鎖等方法使用各種場景,給synchronized性能帶來了很大的提升,

1. 鎖膨脹

上面講到鎖有四種狀態,并且會因實際情況進行膨脹升級,其膨脹方向是:無鎖——>偏向鎖——>輕量級鎖——>重量級鎖,并且膨脹方向不可逆,

偏向鎖

一句話總結它的作用:減少統一執行緒獲取鎖的代價,在大多數情況下,鎖不存在多執行緒競爭,總是由同一執行緒多次獲得,那么此時就是偏向鎖,

核心思想:

如果一個執行緒獲得了鎖,那么鎖就進入偏向模式,此時Mark Word的結構也就變為偏向鎖結構,當該執行緒再次請求鎖時,無需再做任何同步操作,即獲取鎖的程序只需要檢查**Mark Word的鎖標記位為偏向鎖以及當前執行緒ID等于Mark Word**的ThreadID即可,這樣就省去了大量有關鎖申請的操作,

輕量級鎖

輕量級鎖是由偏向鎖升級而來,當存在第二個執行緒申請同一個鎖物件時,偏向鎖就會立即升級為輕量級鎖,注意這里的第二個執行緒只是申請鎖,不存在兩個執行緒同時競爭鎖,可以是一前一后地交替執行同步塊,

重量級鎖

重量級鎖是由輕量級鎖升級而來,當同一時間有多個執行緒競爭鎖時,鎖就會被升級成重量級鎖,此時其申請鎖帶來的開銷也就變大,

重量級鎖一般使用場景會在追求吞吐量,同步塊或者同步方法執行時間較長的場景,

2.鎖消除

消除鎖是虛擬機另外一種鎖的優化,這種優化更徹底,在JIT編譯時,對運行背景關系進行掃描,去除不可能存在競爭的鎖,比如下面代碼的method1和method2的執行效率是一樣的,因為object鎖是私有變數,不存在所得競爭關系,

image-20210822141520951

3. 鎖粗化

鎖粗化是虛擬機對另一種極端情況的優化處理,通過擴大鎖的范圍,避免反復加鎖和釋放鎖,比如下面method3經過鎖粗化優化之后就和method4執行效率一樣了,

image-20210822141439642

4. 自旋鎖與自適應自旋鎖

輕量級鎖失敗后,虛擬機為了避免執行緒真實地在作業系統層面掛起,還會進行一項稱為自旋鎖的優化手段,

自旋鎖:許多情況下,共享資料的鎖定狀態持續時間較短,切換執行緒不值得,通過讓執行緒執行回圈等待鎖的釋放,不讓出CPU,如果得到鎖,就順利進入臨界區,如果還不能獲得鎖,那就會將執行緒在作業系統層面掛起,這就是自旋鎖的優化方式,但是它也存在缺點:如果鎖被其他執行緒長時間占用,一直不釋放CPU,會帶來許多的性能開銷,

自適應自旋鎖:這種相當于是對上面自旋鎖優化方式的進一步優化,它的自旋的次數不再固定,其自旋的次數由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定,這就解決了自旋鎖帶來的缺點,

為什么要引入偏向鎖和輕量級鎖?為什么重量級鎖開銷大?

重量級鎖底層依賴于系統的同步函式來實作,在 linux 中使用 pthread_mutex_t(互斥鎖)來實作,

這些底層的同步函式操作會涉及到:作業系統用戶態和內核態的切換、行程的背景關系切換,而這些操作都是比較耗時的,因此重量級鎖操作的開銷比較大,

而在很多情況下,可能獲取鎖時只有一個執行緒,或者是多個執行緒交替獲取鎖,在這種情況下,使用重量級鎖就不劃算了,因此引入了偏向鎖和輕量級鎖來降低沒有并發競爭時的鎖開銷,

28. synchronized 鎖能降級嗎?

可以的,

具體的觸發時機:在全域安全點(safepoint)中,執行清理任務的時候會觸發嘗試降級鎖,

當鎖降級時,主要進行了以下操作:

1)恢復鎖物件的 markword 物件頭;

2)重置 ObjectMonitor,然后將該 ObjectMonitor 放入全域空閑串列,等待后續使用,

29. ThreadLocal是什么?

ThreadLocal,即執行緒本地變數,如果你創建了一個ThreadLocal變數,那么訪問這個變數的每個執行緒都會有這個變數的一個本地拷貝,多個執行緒操作這個變數的時候,實際是操作自己本地記憶體里面的變數,從而起到執行緒隔離的作用,避免了執行緒安全問題,

1

//創建一個ThreadLocal變數 static ThreadLocalString> localVariable = new ThreadLocal();

ThreadLocal的應用場景有

  • 資料庫連接池

  • 會話管理中使用

30. ThreadLocal的實作原理

  • Thread類有一個型別為ThreadLocal.ThreadLocalMap的實體變數threadLocals,即每個執行緒都有一個屬于自己的ThreadLocalMap,

  • ThreadLocalMap內部維護著Entry陣列,每個Entry代表一個完整的物件,key是ThreadLocal本身,value是ThreadLocal的泛型值,

  • 每個執行緒在往ThreadLocal里設定值的時候,都是往自己的ThreadLocalMap里存,讀也是以某個ThreadLocal作為參考,在自己的map里找對應的key,從而實作了執行緒隔離,

ThreadLocal記憶體結構圖:

img

由結構圖是可以看出:

  • Thread物件中持有一個ThreadLocal.ThreadLocalMap的成員變數,

  • ThreadLocalMap內部維護了Entry陣列,每個Entry代表一個完整的物件,key是ThreadLocal本身,value是ThreadLocal的泛型值,

31. 知道ThreadLocal 記憶體泄露問題嗎?

先看看一下的TreadLocal的參考示意圖哈,

img

ThreadLocalMap中使用的 key 為 ThreadLocal 的弱參考,如下

img

弱參考:只要垃圾回識訓制一運行,不管JVM的記憶體空間是否充足,都會回收該物件占用的記憶體,

弱參考比較容易被回收,因此,如果ThreadLocal(ThreadLocalMap的Key)被垃圾回收器回收了,但是因為ThreadLocalMap生命周期和Thread是一樣的,它這時候如果不被回收,就會出現這種情況:ThreadLocalMap的key沒了,value還在,這就會「造成了記憶體泄漏問題」

如何「解決記憶體泄漏問題」?使用完ThreadLocal后,及時呼叫remove()方法釋放記憶體空間,

32. 了解ReentrantLock嗎?

ReetrantLock是一個可重入的獨占鎖,主要有兩個特性,一個是支持公平鎖和非公平鎖,一個是可重入, ReetrantLock實作依賴于AQS(AbstractQueuedSynchronizer),

ReetrantLock主要依靠AQS維護一個阻塞佇列,多個執行緒對加鎖時,失敗則會進入阻塞佇列,等待喚醒,重新嘗試加鎖,

33. ReadWriteLock是什么?

首先ReentrantLock某些時候有局限,如果使用ReentrantLock,可能本身是為了防止執行緒A在寫資料、執行緒B在讀資料造成的資料不一致,但這樣,如果執行緒C在讀資料、執行緒D也在讀資料,讀資料是不會改變資料的,沒有必要加鎖,但是還是加鎖了,降低了程式的性能,

因為這個,才誕生了讀寫鎖ReadWriteLock,ReadWriteLock是一個讀寫鎖介面,ReentrantReadWriteLock是ReadWriteLock介面的一個具體實作,實作了讀寫的分離,讀鎖是共享的,寫鎖是獨占的,讀和讀之間不會互斥,讀和寫、寫和讀、寫和寫之間才會互斥,提升了讀寫的性能

執行緒池專題

1. 為什么要用執行緒池?

執行緒池提供了一種限制和管理資源(包括執行一個任務), 每個執行緒池還維護一些基本統計資訊,例如已完成任務的數量,

使用執行緒池的好處:

  • 降低資源消耗, 通過重復利用已創建的執行緒降低執行緒創建和銷毀造成的消耗,

  • 提高回應速度, 當任務到達時,任務可以不需要的等到執行緒創建就能立即執行,

  • 提高執行緒的可管理性, 執行緒是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一的分配,調優和監控,

2. 執行execute()方法和submit()方法的區別是什么呢?

  • execute() 方法用于提交不需要回傳值的任務,所以無法判斷任務是否被執行緒池執行成功與否;

  • submit()方法用于提交需要回傳值的任務,執行緒池會回傳一個future型別的物件,通過這個future物件可以判斷任務是否執行成功,并且可以通過future的get()方法來獲取回傳值,get()方***阻塞當前執行緒直到任務完成,而使用 get(long timeout,TimeUnit unit)方法則會阻塞當前執行緒一段時間后立即回傳,這時候有可能任務沒有執行完,

3. 你說下執行緒池核心引數?


  • corePoolSize : 核心執行緒大小,執行緒池一直運行,核心執行緒就不會停止,

  • maximumPoolSize :執行緒池最大執行緒數量,非核心執行緒數量=maximumPoolSize-corePoolSize

  • keep AliveTime :非核心執行緒的心跳時間,如果非核心執行緒在keepAliveTime內沒有運行任務,非核心執行緒會消亡,

  • workQueue :阻塞佇列,ArrayBlockingQueue,LinkedBlockingQueue等,用來存放執行緒任務,

  • defaultHandler :飽和策略,ThreadPoolExecutor類中一共有4種飽和策略,通過實作RejectedExecutionHandler介面,

    • AbortPolicy : 執行緒任務丟棄報錯,默認飽和策略,

    • DiscardPolicy : 執行緒任務直接丟棄不報錯,

    • DiscardOldestPolicy : 將workQueue隊首任務丟棄,將最新執行緒任務重新加入佇列執行,

    • CallerRunsPolicy :執行緒池之外的執行緒直接呼叫run方法執行,

  • ThreadFactory :執行緒工廠,新建執行緒工廠,

4. 執行緒池執行任務的流程?

  1. 執行緒池執行execute/submit方法向執行緒池添加任務,當任務小于核心執行緒數corePoolSize,執行緒池中可以創建新的執行緒,

  2. 當任務大于核心執行緒數corePoolSize,就向阻塞佇列添加任務,

  3. 如果阻塞佇列已滿,需要通過比較引數maximumPoolSize,在執行緒池創建新的執行緒,當執行緒數量大于maximumPoolSize,說明當前設定執行緒池中執行緒已經處理不了了,就會執行飽和策略,

5. 常用的JAVA執行緒池有哪幾種型別?

1、newCachedThreadPool

創建一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閑執行緒,若無可回收,則新建執行緒,

這種型別的執行緒池特點是:

作業執行緒的創建數量幾乎沒有限制(其實也有限制的,數目為Interger. MAX_VALUE), 這樣可靈活的往執行緒池中添加執行緒,

如果長時間沒有往執行緒池中提交任務,即如果作業執行緒空閑了指定的時間(默認為1分鐘),則該作業執行緒將自動終止,終止后,如果你又提交了新的任務,則執行緒池重新創建一個作業執行緒,

在使用CachedThreadPool時,一定要注意控制任務的數量,否則,由于大量執行緒同時運行,很有會造成系統OOM,

2、newFixedThreadPool

創建一個指定作業執行緒數量的執行緒池,每當提交一個任務就創建一個作業執行緒,如果作業執行緒數量達到執行緒池初始的最大數,則將提交的任務存入到池佇列中,

FixedThreadPool是一個典型且優秀的執行緒池,它具有執行緒池提高程式效率和節省創建執行緒時所耗的開銷的優點,但是,在執行緒池空閑時,即執行緒池中沒有可運行任務時,它不會釋放作業執行緒,還會占用一定的系統資源,

3、newSingleThreadExecutor

創建一個單執行緒化的Executor,即只創建唯一的作業者執行緒來執行任務,它只會用唯一的作業執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行,如果這個執行緒例外結束,會有另一個取代它,保證順序執行,單作業執行緒最大的特點是可保證順序地執行各個任務,并且在任意給定的時間不會有多個執行緒是活動的,

4、newScheduleThreadPool

創建一個定長的執行緒池,而且支持定時的以及周期性的任務執行,支持定時及周期性任務執行,

6. 執行緒池常用的阻塞佇列有哪些?

阻塞佇列

表格左側是執行緒池,右側為它們對應的阻塞佇列,可以看到 5 種執行緒池對應了 3 種阻塞佇列

  1. LinkedBlockingQueue 對于 FixedThreadPool 和 SingleThreadExector 而言,它們使用的阻塞佇列是容量為 Integer.MAX_VALUE 的 LinkedBlockingQueue,可以認為是無界佇列,由于 FixedThreadPool 執行緒池的執行緒數是固定的,所以沒有辦法增加特別多的執行緒來處理任務,這時就需要 LinkedBlockingQueue 這樣一個沒有容量限制的阻塞佇列來存放任務,

    這里需要注意,由于執行緒池的任務佇列永遠不會放滿,所以執行緒池只會創建核心執行緒數量的執行緒,所以此時的最大執行緒數對執行緒池來說沒有意義,因為并不會觸發生成多于核心執行緒數的執行緒,

  2. SynchronousQueue 第二種阻塞佇列是 SynchronousQueue,對應的執行緒池是 CachedThreadPool,執行緒池 CachedThreadPool 的最大執行緒數是 Integer 的最大值,可以理解為執行緒數是可以無限擴展的,CachedThreadPool 和上一種執行緒池 FixedThreadPool 的情況恰恰相反,FixedThreadPool 的情況是阻塞佇列的容量是無限的,而這里 CachedThreadPool 是執行緒數可以無限擴展,所以 CachedThreadPool 執行緒池并不需要一個任務佇列來存盤任務,因為一旦有任務被提交就直接轉發給執行緒或者創建新執行緒來執行,而不需要另外保存它們, 我們自己創建使用 SynchronousQueue 的執行緒池時,如果不希望任務被拒絕,那么就需要注意設定最大執行緒數要盡可能大一些,以免發生任務數大于最大執行緒數時,沒辦法把任務放到佇列中也沒有足夠執行緒來執行任務的情況,

  3. DelayedWorkQueue 第三種阻塞佇列是DelayedWorkQueue,它對應的執行緒池分別是 ScheduledThreadPool 和 SingleThreadScheduledExecutor,這兩種執行緒池的最大特點就是可以延遲執行任務,比如說一定時間后執行任務或是每隔一定的時間執行一次任務,

DelayedWorkQueue 的特點是內部元素并不是按照放入的時間 排序,而是會按照延遲的時間長短對任務進行排序,內部采用的是“堆”的資料結構,之所以執行緒池 ScheduledThreadPool 和 SingleThreadScheduledExecutor 選擇 DelayedWorkQueue,是因為它們本身正是基于時間執行任務的,而延遲佇列正好可以把任務按時間進行 排序,方便任務的執行,

7. 原始碼中執行緒池是怎么復用執行緒的?


原始碼中ThreadPoolExecutor中有個內置物件Worker,每個worker都是一個執行緒,worker執行緒數量和引數有關,每個worker會while死回圈從阻塞佇列中取資料,通過置換worker中Runnable物件,運行其run方法起到執行緒置換的效果,這樣做的好處是避免多執行緒頻繁執行緒切換,提高程式運行性能,

8. 如何合理配置執行緒池引數?

自定義執行緒池就需要我們自己配置最大執行緒數 maximumPoolSize ,為了高效的并發運行,這時需要看我們的業務是IO密集型還是CPU密集型,

CPU密集型 CPU密集的意思是該任務需要最大的運算,而沒有阻塞,CPU一直全速運行,CPU密集任務只有在真正的多核CPU上才能得到加速(通過多執行緒),而在單核CPU上,無論你開幾個模擬的多執行緒該任務都不可能得到加速,因為CPU總的運算能力就那么多,

IO密集型 IO密集型,即該任務需要大量的IO,即大量的阻塞,在單執行緒上運行IO密集型的任務會導致大量的CPU運算能力浪費在等待,所以在IO密集型任務中使用多執行緒可以大大的加速程式運行,即使在單核CPU上這種加速主要就是利用了被浪費掉的阻塞時間,

IO 密集型時,大部分執行緒都阻塞,故需要多配制執行緒數,公式為:

1

CPU核數*2 CPU核數/(1-阻塞系數) 阻塞系數在0.8~0.9之間 查看CPU核數: System.out.println(Runtime.getRuntime().availableProcessors());

9. Executor和Executors的區別?

Executors 工具類的不同方法按照我們的需求創建了不同的執行緒池,來滿足業務的需求,

Executor 介面物件能執行我們的執行緒任務,ExecutorService介面繼承了Executor介面并進行了擴展,提供了更多的方法我們能獲得任務執行的狀態并且可以獲取任務的回傳值,

使用ThreadPoolExecutor 可以創建自定義執行緒池,Future 表示異步計算的結果,他提供了檢查計算是否完成的方法,以等待計算的完成,并可以使用get()方法獲取計算的結果,

AQS

1. 說一說什么是AQS?

  1. AQS 是一個鎖框架,它定義了鎖的實作機制,并開放出擴展的地方,讓子類去實作,比如我們在 lock 的時候,AQS 開放出 state 欄位,讓子類可以根據 state 欄位來決定是否能夠獲得鎖,對于獲取不到鎖的執行緒 AQS 會自動進行管理,無需子類鎖關心,這就是 lock 時鎖的內部機制,封裝的很好,又暴露出子類鎖需要擴展的地方;

  2. AQS 底層是由同步佇列 + 條件佇列聯手組成,同步佇列管理著獲取不到鎖的執行緒的排隊和釋放,條件佇列是在一定場景下,對同步佇列的補充,比如獲得鎖的執行緒從空佇列中拿資料,肯定是拿不到資料的,這時候條件佇列就會管理該執行緒,使該執行緒阻塞;

  3. AQS 圍繞兩個佇列,提供了四大場景,分別是:獲得鎖、釋放鎖、條件佇列的阻塞,條件佇列的喚醒,分別對應著 AQS 架構圖中的四種顏色的線的走向,

2. AQS使用了哪些設計模式?

AQS同步器的設計是基于模板方法模式的,如果需要自定義同步器一般的方式是這樣(模板方法模式很經典的一個應用):

  1. 使用者繼承AbstractQueuedSynchronizer并重寫指定的方法,(這些重寫方法很簡單,無非是對于共享資源state的獲取和釋放)

  2. 將AQS組合在自定義同步組件的實作中,并呼叫其模板方法,而這些模板方***呼叫使用者重寫的方法,

這和我們以往通過實作介面的方式有很大區別,這是模板方法模式很經典的一個運用,

AQS使用了模板方法模式,自定義同步器時需要重寫下面幾個AQS提供的模板方法:

1

isHeldExclusively()//該執行緒是否正在獨占資源,只有用到condition才需要去實作它, tryAcquire(int)//獨占方式,嘗試獲取資源,成功則回傳true,失敗則回傳false, tryRelease(int)//獨占方式,嘗試釋放資源,成功則回傳true,失敗則回傳false, tryAcquireShared(int)//共享方式,嘗試獲取資源,負數表示失敗;0表示成功,但沒有剩余可用資源;正數表示成功,且有剩余資源, tryReleaseShared(int)//共享方式,嘗試釋放資源,成功則回傳true,失敗則回傳false,

3. 了解AQS中同步佇列的資料結構嗎?

image-20210822170028290

  • 當前執行緒獲取同步狀態失敗,同步器將當前執行緒機等待狀態等資訊構造成一個Node節點加入佇列,放在隊尾,同步器重新設定尾節點

  • 加入佇列后,會阻塞當前執行緒

  • 同步狀態被釋放并且同步器重新設定首節點,同步器喚醒等待佇列中第一個節點,讓其再次獲取同步狀態

4. 了解AQS 對資源的共享方式嗎?

AQS定義兩種資源共享方式

  • Exclusive

    (獨占):只有一個執行緒能執行,如ReentrantLock,又可分為公平鎖和非公平鎖:

    • 公平鎖:按照執行緒在佇列中的排隊順序,先到者先拿到鎖

    • 非公平鎖:當執行緒要獲取鎖時,無視佇列順序直接去搶鎖,誰搶到就是誰的

  • Share(共享):多個執行緒可同時執行,如Semaphore/CountDownLatch,Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我們都會在后面講到,

ReentrantReadWriteLock 可以看成是組合式,因為ReentrantReadWriteLock也就是讀寫鎖允許多個執行緒同時對某一資源進行讀,

不同的自定義同步器爭用共享資源的方式也不同,自定義同步器在實作時只需要實作共享資源 state 的獲取與釋放方式即可,至于具體執行緒等待佇列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在頂層實作好了,

5. AQS 組件了解嗎?

  • Semaphore(信號量)-允許多個執行緒同時訪問: synchronized 和 ReentrantLock 都是一次只允許一個執行緒訪問某個資源,Semaphore(信號量)可以指定多個執行緒同時訪問某個資源,

  • CountDownLatch (倒計時器): CountDownLatch是一個同步工具類,用來協調多個執行緒之間的同步,這個工具通常用來控制執行緒等待,它可以讓某一個執行緒等待直到倒計時結束,再開始執行,

  • CyclicBarrier(回圈柵欄): CyclicBarrier 和 CountDownLatch 非常類似,它也可以實作執行緒間的技術等待,但是它的功能比 CountDownLatch 更加復雜和強大,主要應用場景和 CountDownLatch 類似,CyclicBarrier 的字面意思是可回圈使用(Cyclic)的屏障(Barrier),它要做的事情是,讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最后一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續干活,CyclicBarrier默認的構造方法是 CyclicBarrier(int parties),其引數表示屏障攔截的執行緒數量,每個執行緒呼叫await方法告訴 CyclicBarrier 我已經到達了屏障,然后當前執行緒被阻塞,

Atomic 原子類

1. 介紹一下 Atomic 原子類

Atomic 是指一個操作是不可中斷的,即使是在多個執行緒一起執行的時候,一個操作一旦開始,就不會被其他執行緒干擾,

所以,所謂原子類說簡單點就是具有原子 / 原子操作特征的類,

并發包 java.util.concurrent 的原子類都存放在 java.util.concurrent.atomic 下:

atomic

2. JUC 包中的原子類是哪4類?

基本型別 使用原子的方式更新基本型別:

  • AtomicInteger : 整型原子類

  • AtomicLong: 長整型原子類

  • AtomicBoolean: 布爾型原子類

陣列型別 使用原子的方式更新陣列里的某個元素:

  • AtomicIntegerArray: 整型陣列原子類

  • AtomicLongArray: 長整型陣列原子類

  • AtomicReferenceArray: 參考型別陣列原子類

參考型別 使用原子的方式更新參考型別:

  • AtomicReference: 參考型別原子類

  • AtomicStampedReference: 原子更新帶有版本號的參考型別,該類將整型數值與參考關聯起來,可用于解決原子的更新資料和資料的版本號,可以解決使用 CAS 進行原子更新時可能出現的 ABA 問題,

  • AtomicMarkableReference: 原子更新帶有標記位的參考型別,物件屬性修改型別

  • AtomicIntegerFieldUpdater: 原子更新整型欄位的更新器

  • AtomicLongFieldUpdater: 原子更新長整型欄位的更新器

  • AtomicMarkableReference: 原子更新帶有標記位的參考型別

3. 簡單介紹一下 AtomicInteger 類的原理

AtomicInteger 類主要利用 CAS和 volatile 和 native 方法來保證原子操作,從而避免 synchronized 的高開銷,執行效率大為提升,

AtomicInteger 類的部分原始碼:

1

// 更新操作時提供“比較并替換”的作用 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try{ valueOffset = unsafe.objectFieldOffset(AutomicInteger.class.getDeclaredField("value")); }catch(Exception ex){ throw new Error(ex); } } private volatile int value;

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/302776.html

標籤:java

上一篇:【演算法】圖解八大排序

下一篇:新手小白學JAVA 日期類Date SimoleDateFormat Calendar

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more