Java併發面試題精選

程式設計師生態圈發表於2018-08-10

1,什麼是執行緒?

執行緒是作業系統能夠進行運算排程的最小單位,它被包含在程式之中,是程式中的實際運作單位。程式設計師可以通過它進行多處理器程式設計,你可以使用多執行緒對運算密集型任務提速。比如,如果一個執行緒完成一個任務要100毫秒,那麼用十個執行緒完成改任務只需10毫秒。

2,執行緒和程式有什麼區別?

執行緒是程式的子集,一個程式可以有很多執行緒,每條執行緒並行執行不同的任務。不同的程式使用不同的記憶體空間,而所有的執行緒共享一片相同的記憶體空間。每個執行緒都擁有單獨的棧記憶體用來儲存本地資料。

3,如何在Java中實現執行緒?

兩種方式:java.lang.Thread 類的例項就是一個執行緒但是它需要呼叫java.lang.Runnable介面來執行,由於執行緒類本身就是呼叫的Runnable介面所以你可以繼承java.lang.Thread 類或者直接呼叫Runnable介面來重寫run()方法實現執行緒。

4,Java 關鍵字volatile 與 synchronized 作用與區別?

1,volatile

它所修飾的變數不保留拷貝,直接訪問主記憶體中的。

在Java記憶體模型中,有main memory,每個執行緒也有自己的memory (例如暫存器)。為了效能,一個執行緒會在自己的memory中保持要訪問的變數的副本。這樣就會出現同一個變 量在某個瞬間,在一個執行緒的memory中的值可能與另外一個執行緒memory中的值,或者main memory中的值不一致的情況。 一個變數宣告為volatile,就意味著這個變數是隨時會被其他執行緒修改的,因此不能將它cache線上程memory中。

2,synchronized

當它用來修飾一個方法或者一個程式碼塊的時候,能夠保證在同一時刻最多隻有一個執行緒執行該段程式碼。

一、當兩個併發執行緒訪問同一個物件object中的這個synchronized(this)同步程式碼塊時,一個時間內只能有一個執行緒得到執行。另一個執行緒必須等待當前執行緒執行完這個程式碼塊以後才能執行該程式碼塊。

二、然而,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,另一個執行緒仍然可以訪問該object中的非synchronized(this)同步程式碼塊。

三、尤其關鍵的是,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,其他執行緒對object中所有其它synchronized(this)同步程式碼塊的訪問將被阻塞。

四、當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,它就獲得了這個object的物件鎖。結果,其它執行緒對該object物件所有同步程式碼部分的訪問都被暫時阻塞。

五、以上規則對其它物件鎖同樣適用。

 

5,有哪些不同的執行緒生命週期?

當我們在Java程式中新建一個執行緒時,它的狀態是New。當我們呼叫執行緒的start()方法時,狀態被改變為Runnable。執行緒排程器會為Runnable執行緒池中的執行緒分配CPU時間並且講它們的狀態改變為Running。其他的執行緒狀態還有Waiting,Blocked 和Dead。

6,你對執行緒優先順序的理解是什麼?

每一個執行緒都是有優先順序的,一般來說,高優先順序的執行緒在執行時會具有優先權,但這依賴於執行緒排程的實現,這個實現是和作業系統相關的(OS dependent)。我們可以定義執行緒的優先順序,但是這並不能保證高優先順序的執行緒會在低優先順序的執行緒前執行。執行緒優先順序是一個int變數(從1-10),1代表最低優先順序,10代表最高優先順序。

7,什麼是死鎖(Deadlock)?如何分析和避免死鎖?

死鎖是指兩個以上的執行緒永遠阻塞的情況,這種情況產生至少需要兩個以上的執行緒和兩個以上的資源。

分析死鎖,我們需要檢視Java應用程式的執行緒轉儲。我們需要找出那些狀態為BLOCKED的執行緒和他們等待的資源。每個資源都有一個唯一的id,用這個id我們可以找出哪些執行緒已經擁有了它的物件鎖。

避免巢狀鎖,只在需要的地方使用鎖和避免無限期等待是避免死鎖的通常辦法。

8,什麼是執行緒安全?Vector是一個執行緒安全類嗎?

如果你的程式碼所在的程式中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。一個執行緒安全的計數器類的同一個例項物件在被多個執行緒使用的情況下也不會出現計算失誤。很顯然你可以將集合類分成兩組,執行緒安全和非執行緒安全的。Vector 是用同步方法來實現執行緒安全的, 而和它相似的ArrayList不是執行緒安全的。

9,Java中如何停止一個執行緒?

Java提供了很豐富的API但沒有為停止執行緒提供API。JDK 1.0本來有一些像stop(), suspend() 和 resume()的控制方法但是由於潛在的死鎖威脅因此在後續的JDK版本中他們被棄用了,之後Java API的設計者就沒有提供一個相容且執行緒安全的方法來停止一個執行緒。當run() 或者 call() 方法執行完的時候執行緒會自動結束,如果要手動結束一個執行緒,你可以用volatile 布林變數來退出run()方法的迴圈或者是取消任務來中斷執行緒

10,什麼是ThreadLocal?

ThreadLocal用於建立執行緒的本地變數,我們知道一個物件的所有執行緒會共享它的全域性變數,所以這些變數不是執行緒安全的,我們可以使用同步技術。但是當我們不想使用同步的時候,我們可以選擇ThreadLocal變數。

每個執行緒都會擁有他們自己的Thread變數,它們可以使用get()set()方法去獲取他們的預設值或者線上程內部改變他們的值。ThreadLocal例項通常是希望它們同執行緒狀態關聯起來是private static屬性。

11,Sleep()、suspend()和wait()之間有什麼區別?

Thread.sleep()使當前執行緒在指定的時間處於“非執行”(Not Runnable)狀態。執行緒一直持有物件的監視器。比如一個執行緒當前在一個同步塊或同步方法中,其它執行緒不能進入該塊或方法中。如果另一執行緒呼叫了interrupt()方法,它將喚醒那個“睡眠的”執行緒。

注意:sleep()是一個靜態方法。這意味著只對當前執行緒有效,一個常見的錯誤是呼叫t.sleep(),(這裡的t是一個不同於當前執行緒的執行緒)。即便是執行t.sleep(),也是當前執行緒進入睡眠,而不是t執行緒。t.suspend()是過時的方法,使用suspend()導致執行緒進入停滯狀態,該執行緒會一直持有物件的監視器,suspend()容易引起死鎖問題。

object.wait()使當前執行緒出於“不可執行”狀態,和sleep()不同的是wait是object的方法而不是thread。呼叫object.wait()時,執行緒先要獲取這個物件的物件鎖,當前執行緒必須在鎖物件保持同步,把當前執行緒新增到等待佇列中,隨後另一執行緒可以同步同一個物件鎖來呼叫object.notify(),這樣將喚醒原來等待中的執行緒,然後釋放該鎖。基本上wait()/notify()與sleep()/interrupt()類似,只是前者需要獲取物件鎖。

12,什麼是執行緒餓死,什麼是活鎖?

當所有執行緒阻塞,或者由於需要的資源無效而不能處理,不存在非阻塞執行緒使資源可用。JavaAPI中執行緒活鎖可能發生在以下情形:

1,當所有執行緒在程式中執行Object.wait(0),引數為0的wait方法。程式將發生活鎖直到在相應的物件上有執行緒呼叫Object.notify()或者Object.notifyAll()。

2,當所有執行緒卡在無限迴圈中。

13,什麼是Java Timer類?如何建立一個有特定時間間隔的任務?

java.util.Timer是一個工具類,可以用於安排一個執行緒在未來的某個特定時間執行。Timer類可以用安排一次性任務或者週期任務。

java.util.TimerTask是一個實現了Runnable介面的抽象類,我們需要去繼承這個類來建立我們自己的定時任務並使用Timer去安排它的執行。

14,Java中的同步集合與併發集合有什麼區別?

同步集合與併發集合都為多執行緒和併發提供了合適的執行緒安全的集合,不過併發集合的可擴充套件性更高。

在Java1.5之前程式設計師們只有同步集合來用且在多執行緒併發的時候會導致爭用,阻礙了系統的擴充套件性。

Java5介紹了併發集合像ConcurrentHashMap,不僅提供執行緒安全還用鎖分離和 內部分割槽等現代技術提高了可擴充套件性。

15,同步方法和同步塊,哪個是更好的選擇?

同步塊是更好的選擇,因為它不會鎖住整個物件(當然你也可以讓它鎖住整個物件)。同步方法會鎖住整個物件,哪怕這個類中有多個不相關聯的同步塊,這通常會導致他們停止執行並需要等待獲得這個物件上的鎖。

16,什麼是執行緒池? 為什麼要使用它?

建立執行緒要花費昂貴的資源和時間,如果任務來了才建立執行緒那麼響應時間會變長,而且一個程式能建立的執行緒數有限。

為了避免這些問題,在程式啟動的時候就建立若干執行緒來響應處理,它們被稱為執行緒池,裡面的執行緒叫工作執行緒。

從JDK1.5開始,Java API提供了Executor框架讓你可以建立不同的執行緒池。比如單執行緒池,每次處理一個任務;數目固定的執行緒池或者是快取執行緒池(一個適合很多生存期短的任務的程式的可擴充套件執行緒池)。

17,Java中invokeAndWait 和 invokeLater有什麼區別?

這兩個方法是Swing API 提供給Java開發者用來從當前執行緒而不是事件派發執行緒更新GUI元件用的。InvokeAndWait()同步更新GUI元件,比如一個進度條,一旦進度更新了,進度條也要做出相應改變。如果進度被多個執行緒跟蹤,那麼就呼叫invokeAndWait()方法請求事件派發執行緒對元件進行相應更新。而invokeLater()方法是非同步呼叫更新元件的。

18,多執行緒中的忙迴圈是什麼?

忙迴圈就是程式設計師用迴圈讓一個執行緒等待,不像傳統方法wait(), sleep() 或 yield() 它們都放棄了CPU控制,而忙迴圈不會放棄CPU,它就是在執行一個空迴圈。這麼做的目的是為了保留CPU快取。

在多核系統中,一個等待執行緒醒來的時候可能會在另一個核心執行,這樣會重建快取。為了避免重建快取和減少等待重建的時間就可以使用它了。

19,Java記憶體模型是什麼?

Java記憶體模型規定和指引Java程式在不同的記憶體架構、CPU和作業系統間有確定性地行為。它在多執行緒的情況下尤其重要。Java記憶體模型對一個執行緒所做的變動能被其它執行緒可見提供了保證,它們之間是先行發生關係。這個關係定義了一些規則讓程式設計師在併發程式設計時思路更清晰。比如,先行發生關係確保了:

執行緒內的程式碼能夠按先後順序執行,這被稱為程式次序規則。

對於同一個鎖,一個解鎖操作一定要發生在時間上後發生的另一個鎖定操作之前,也叫做管程鎖定規則。

前一個對volatile的寫操作在後一個volatile的讀操作之前,也叫volatile變數規則。

一個執行緒內的任何操作必需在這個執行緒的start()呼叫之後,也叫作執行緒啟動規則。

一個執行緒的所有操作都會線上程終止之前,執行緒終止規則。

一個物件的終結操作必需在這個物件構造完成之後,也叫物件終結規則。

可傳遞性

更多介紹可以移步併發程式設計交流群:628134587

20,Java中interrupted 和isInterruptedd方法的區別?

interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除而後者不會。Java多執行緒的中斷機制是用內部標識來實現的,呼叫Thread.interrupt()來中斷一個執行緒就會設定中斷標識為true。當中斷執行緒呼叫靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。

非靜態方法isInterrupted()用來查詢其它執行緒的中斷狀態且不會改變中斷狀態標識。簡單的說就是任何丟擲InterruptedException異常的方法都會將中斷狀態清零。無論如何,一個執行緒的中斷狀態都有可能被其它執行緒呼叫中斷來改變。

21,Java中的同步集合與併發集合有什麼區別?

同步集合與併發集合都為多執行緒和併發提供了合適的執行緒安全的集合,不過併發集合的可擴充套件性更高。在Java1.5之前程式設計師們只有同步集合來用且在多執行緒併發的時候會導致爭用,阻礙了系統的擴充套件性。Java5介紹了併發集合像ConcurrentHashMap,不僅提供執行緒安全還用鎖分離和內部分割槽等現代技術提高了可擴充套件性。

不管是同步集合還是併發集合他們都支援執行緒安全,他們之間主要的區別體現在效能和可擴充套件性,還有他們如何實現的執行緒安全上。

同步HashMap, Hashtable, HashSet, Vector, ArrayList 相比他們併發的實現(ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteHashSet)會慢得多。造成如此慢的主要原因是鎖, 同步集合會把整個Map或List鎖起來,而併發集合不會。併發集合實現執行緒安全是通過使用先進的和成熟的技術像鎖剝離。

比如ConcurrentHashMap 會把整個Map 劃分成幾個片段,只對相關的幾個片段上鎖,同時允許多執行緒訪問其他未上鎖的片段。

同樣的,CopyOnWriteArrayList 允許多個執行緒以非同步的方式讀,當有執行緒寫的時候它會將整個List複製一個副本給它。

如果在讀多寫少這種對併發集合有利的條件下使用併發集合,這會比使用同步集合更具有可伸縮性。

22,什麼是執行緒池? 為什麼要使用它?

建立執行緒要花費昂貴的資源和時間,如果任務來了才建立執行緒那麼響應時間會變長,而且一個程式能建立的執行緒數有限。為了避免這些問題,在程式啟動的時候就建立若干執行緒來響應處理,它們被稱為執行緒池,裡面的執行緒叫工作執行緒。從JDK1.5開始,Java API提供了Executor框架讓你可以建立不同的執行緒池。比如單執行緒池,每次處理一個任務;數目固定的執行緒池或者是快取執行緒池(一個適合很多生存期短的任務的程式的可擴充套件執行緒池)

執行緒池的作用,就是在呼叫執行緒的時候初始化一定數量的執行緒,有執行緒過來的時候,先檢測初始化的執行緒還有空的沒有,沒有就再看當前執行中的執行緒數是不是已經達到了最大數,如果沒有,就新分配一個執行緒去處理。

就像餐館中吃飯一樣,從裡面叫一個服務員出來;但如果已經達到了最大數,就相當於服務員已經用盡了,那沒得辦法,另外的執行緒就只有等了,直到有新的“服務員”為止。

執行緒池的優點就是可以管理執行緒,有一個高度中樞,這樣程式才不會亂,保證系統不會因為大量的併發而因為資源不足掛掉。

23,Java中活鎖和死鎖有什麼區別?

活鎖:一個執行緒通常會有會響應其他執行緒的活動。如果其他執行緒也會響應另一個執行緒的活動,那麼就有可能發生活鎖。同死鎖一樣,發生活鎖的執行緒無法繼續執行。然而執行緒並沒有阻塞——他們在忙於響應對方無法恢復工作。這就相當於兩個在走廊相遇的人:甲向他自己的左邊靠想讓乙過去,而乙向他的右邊靠想讓甲過去。可見他們阻塞了對方。甲向他的右邊靠,而乙向他的左邊靠,他們還是阻塞了對方。

死鎖:兩個或更多執行緒阻塞著等待其它處於死鎖狀態的執行緒所持有的鎖。死鎖通常發生在多個執行緒同時但以不同的順序請求同一組鎖的時候,死鎖會讓你的程式掛起無法完成任務。

24,如何避免死鎖?

死鎖的發生必須滿足以下四個條件:

互斥條件:一個資源每次只能被一個程式使用。

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

不剝奪條件:程式已獲得的資源,在末使用完之前,不能強行剝奪。

迴圈等待條件:若干程式之間形成一種頭尾相接的迴圈等待資源關係。

三種用於避免死鎖的技術:

加鎖順序(執行緒按照一定的順序加鎖)

加鎖時限(執行緒嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己佔有的鎖)

死鎖檢測

(死鎖原因及如何避免更深理解移步:628134587)

25,notify()和notifyAll()有什麼區別?

1,notify()和notifyAll()都是Object物件用於通知處在等待該物件的執行緒的方法。

2,void notify(): 喚醒一個正在等待該物件的執行緒。

3,void notifyAll(): 喚醒所有正在等待該物件的執行緒。

兩者的最大區別在於:

notifyAll使所有原來在該物件上等待被notify的執行緒統統退出wait的狀態,變成等待該物件上的鎖,一旦該物件被解鎖,他們就會去競爭。

notify他只是選擇一個wait狀態執行緒進行通知,並使它獲得該物件上的鎖,但不驚動其他同樣在等待被該物件notify的執行緒們,當第一個執行緒執行完畢以後釋放物件上的鎖,此時如果該物件沒有再次使用notify語句,即便該物件已經空閒,其他wait狀態等待的執行緒由於沒有得到該物件的通知,繼續處在wait狀態,直到這個物件發出一個notify或notifyAll,它們等待的是被notify或notifyAll,而不是鎖。

26,什麼是可重入鎖(ReentrantLock)?

Java.util.concurrent.lock 中的 Lock 框架是鎖定的一個抽象,它允許把鎖定的實現作為Java 類,而不是作為語言的特性來實現。這就為Lock 的多種實現留下了空間,各種實現可能有不同的排程演算法、效能特性或者鎖定語義。 ReentrantLock 類實現了Lock ,它擁有與synchronized 相同的併發性和記憶體語義,但是新增了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的效能。(換句話說,當許多執行緒都想訪問共享資源時,JVM可以花更少的時候來排程執行緒,把更多時間用在執行執行緒上。)

Reentrant 鎖意味著什麼呢?簡單來說,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個執行緒再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放。這模仿了synchronized 的語義;如果執行緒進入由執行緒已經擁有的監控器保護的synchronized 塊,就允許執行緒繼續進行,當執行緒退出第二個(或者後續)synchronized塊的時候,不釋放鎖,只有執行緒退出它進入的監控器保護的第一個synchronized 塊時,才釋放鎖。

27,讀寫鎖可以用於什麼應用場景?

讀寫鎖可以用於 “多讀少寫” 的場景,讀寫鎖支援多個讀操作併發執行,寫操作只能由一個執行緒來操作

ReadWriteLock對向資料結構相對不頻繁地寫入,但是有多個任務要經常讀取這個資料結構的這類情況進行了優化。ReadWriteLock使得你可以同時有多個讀取者,只要它們都不試圖寫入即可。如果寫鎖已經被其他任務持有,那麼任何讀取者都不能訪問,直至這個寫鎖被釋放為止。

ReadWriteLock 對程式效能的提高主要受制於如下幾個因素:

1,資料被讀取的頻率與被修改的頻率相比較的結果。

2,讀取和寫入的時間

3,有多少執行緒競爭

4,是否在多處理機器上執行

 

相關文章