java 2018面試題-多線程彙總(含解答)


        學習,內容越多、越雜的知識,越須要進行深入的總結,這樣才能記憶深入,將知識變成本身的。這篇文章主要是對多線程的問題進行總結的,所以羅列了本身整理的多線程的問題,都是本身以爲比較經典和一些大企業面試會問到的。這些多線程的問題,有些來源於各大網站、有些來源於本身的思考。可能有些問題網上有、可能有些問題對應的答案也有、也可能有些各位網友也都看過,java

1多線程的幾種實現方式,什麼是線程安全。

Java多線程實現方式主要有四種:繼承Thread類、實現Runnable接口、實現Callable接口經過FutureTask包裝器來建立Thread線程、使用ExecutorService、Callable、Future實現有返回結果的多線程。程序員

其中前兩種方式線程執行完後都沒有返回值,後兩種是帶返回值的。面試

 ==================================================================編程

volatile的原理,做用,能代替鎖麼。

volatile 關鍵字的做用 保證內存的可見性 防止指令重排數組

注意:volatile 並不保證原子性緩存

可見性原理安全

volatile 保證可見性的原理是在每次訪問變量時都會進行一次刷新,所以每次訪問都是主內存中最新的版本。因此 volatile 關鍵字的做用之一就是保證變量修改的實時可見性。服務器

一個很是重要的問題,是每一個學習、應用多線程的Java程序員都必須掌握的。理解volatile關鍵字的做用的前提是要理解Java內存模型,這裏就不講Java內存模型了,能夠參見第31點,volatile關鍵字的做用主要有兩個:數據結構

(1)多線程主要圍繞可見性和原子性兩個特性而展開,使用volatile關鍵字修飾的變量,保證了其在多線程之間的可見性,即每次讀取到volatile變量,必定是最新的數據多線程

(2)代碼底層執行不像咱們看到的高級語言----Java程序這麼簡單,它的執行是Java代碼-->字節碼-->根據字節碼執行對應的C/C++代碼-->C/C++代碼被編譯成彙編語言-->和硬件電路交互,現實中,爲了獲取更好的性能JVM可能會對指令進行重排序,多線程下可能會出現一些意想不到的問題。使用volatile則會對禁止語義重排序,固然這也必定程度上下降了代碼執行效率

從實踐角度而言,volatile的一個重要做用就是和CAS結合,保證了原子性,詳細的能夠參見java.util.concurrent.atomic包下的類,好比AtomicInteger。

 

volatile 和 synchronized區別

一、 volatile 輕量級,只能修飾變量。synchronized重量級,還可修飾方法

二、volatile 只能保證數據的可見性,不能用來同步,由於多個線程併發訪問 volatile 修飾的變量不會 阻塞。

synchronized 不只保證可見性,並且還保證原子性,由於,只有得到了鎖的線程才能進入臨界區,從而保證臨界區中的全部語句都所有執行。多個線程爭搶 synchronized 鎖對象時,會出現阻塞。

   volatile 並不能保證線程安全性。而 synchronized 則可實現線程的安全性

 ==================================================================

3.sleep和wait的區別。

對於sleep()方法,咱們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。

sleep()方法致使了程序暫停執行指定的時間,讓出cpu該其餘線程,可是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。

在調用sleep()方法的過程當中,線程不會釋放對象鎖。

而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備

獲取對象鎖進入運行狀態。

這個問題常問,sleep方法和wait方法均可以用來放棄CPU必定的時間,不一樣點在於若是線程持有某個對象的監視器,sleep方法不會放棄這個對象的監視器,wait方法會放棄這個對象的監視器

 ==================================================================

sleep和sleep(0)的區別。

Sleep 接口均帶有表示睡眠時間長度的參數 timeout。調用以上提到的 Sleep 接口,會有條件地將調用線程從當前處理器上移除,而且有可能將它從線程調度器的可運行隊列中移除。這個條件取決於調用 Sleep 時timeout 參數。

當 timeout = 0, 即 Sleep(0),若是線程調度器的可運行隊列中有大於或等於當前線程優先級的就緒線程存在,操做系統會將當前線程從處理器上移除,調度其餘優先級高的就緒線程運行;若是可運行隊列中的沒有就緒線程或全部就緒線程的優先級均低於當前線程優先級,那麼當前線程會繼續執行,就像沒有調用 Sleep(0)同樣。

當 timeout > 0 時,如:Sleep(1),會引起線程上下文切換:調用線程會從線程調度器的可運行隊列中被移除一段時間,這個時間段約等於 timeout 所指定的時間長度。爲何說約等於呢?是由於睡眠時間單位爲毫秒,這與系統的時間精度有關。一般狀況下,系統的時間精度爲 10 ms,那麼指定任意少於 10 ms但大於 0 ms 的睡眠時間,均會向上求值爲 10 ms。

而調用 SwitchToThread() 方法,若是當前有其餘就緒線程在線程調度器的可運行隊列中,始終會讓出一個時間切片給這些就緒線程,而無論就緒線程的優先級的高低與否

 ==================================================================

Lock與Synchronized的區別 。

二者區別:

1.首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;

2.synchronized沒法判斷是否獲取鎖的狀態,Lock能夠判斷是否獲取到鎖;

3.synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程當中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),不然容易形成線程死鎖;

4.用synchronized關鍵字的兩個線程1和線程2,若是當前線程1得到鎖,線程2線程等待。若是線程1阻塞,線程2則會一直等待下去,而Lock鎖就不必定會等待下去,若是嘗試獲取不到鎖,線程能夠不用一直等待就結束了;

5.synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(二者皆可)

6.Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少許的同步問題。

synchronized是java中的一個關鍵字,也就是說是Java語言內置的特性。那麼爲何會出現Lock呢?

  若是一個代碼塊被synchronized修飾了,當一個線程獲取了對應的鎖,並執行該代碼塊時,其餘線程便只能一直等待,等待獲取鎖的線程釋放鎖,而這裏獲取鎖的線程釋放鎖只會有兩種狀況:

1)獲取鎖的線程執行完了該代碼塊,而後線程釋放對鎖的佔有;

2)線程執行發生異常,此時JVM會讓線程自動釋放鎖。

  那麼若是這個獲取鎖的線程因爲要等待IO或者其餘緣由(好比調用sleep方法)被阻塞了,可是又沒有釋放鎖,其餘線程便只能乾巴巴地等待,試想一下,這多麼影響程序執行效率。

  所以就須要有一種機制能夠不讓等待的線程一直無期限地等待下去(好比只等待必定的時間或者可以響應中斷),經過Lock就能夠辦到。

  再舉個例子:當有多個線程讀寫文件時,讀操做和寫操做會發生衝突現象,寫操做和寫操做會發生衝突現象,可是讀操做和讀操做不會發生衝突現象。

  可是採用synchronized關鍵字來實現同步的話,就會致使一個問題:

  若是多個線程都只是進行讀操做,因此當一個線程在進行讀操做時,其餘線程只能等待沒法進行讀操做。

  所以就須要一種機制來使得多個線程都只是進行讀操做時,線程之間不會發生衝突,經過Lock就能夠辦到。

  另外,經過Lock能夠知道線程有沒有成功獲取到鎖。這個是synchronized沒法辦到的。

  總結一下,也就是說Lock提供了比synchronized更多的功能。可是要注意如下幾點:

1)Lock不是Java語言內置的,synchronized是Java語言的關鍵字,所以是內置特性。Lock是一個類,經過這個類能夠實現同步訪問;

2)Lock和synchronized有一點很是大的不一樣,採用synchronized不須要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完以後,系統會自動讓線程釋放對鎖的佔用;而Lock則必需要用戶去手動釋放鎖,若是沒有主動釋放鎖,就有可能致使出現死鎖現象。

 

 ==================================================================

6 synchronized的原理是什麼,通常用在什麼地方(好比加在靜態方法和非靜態方法的區別,靜態方法和非靜態方法同時執行的時候會有影響嗎),解釋如下名詞:重排序,自旋鎖,偏向鎖,輕量級鎖,可重入鎖,公平鎖,非公平鎖,樂觀鎖,悲觀鎖。

使用獨佔鎖機制來解決,是一種悲觀的併發策略,抱着一副「總有刁民想害朕」的態勢,每次操做數據的時候都認爲別的線程會參與競爭修改,因此直接加鎖。同一刻只能有一個線程持有鎖,那其餘線程就會阻塞。線程的掛起恢復會帶來很大的性能開銷,儘管jvm對於非競爭性的鎖的獲取和釋放作了不少優化,可是一旦有多個線程競爭鎖,頻繁的阻塞喚醒,仍是會有很大的性能開銷的。因此,使用synchronized或其餘重量級鎖來處理顯然不夠合

樂觀的解決方案,顧名思義,就是很大度樂觀,每次操做數據的時候,都認爲別的線程不會參與競爭修改,也不加鎖。若是操做成功了那最好;若是失敗了,好比中途確有別的線程進入並修改了數據(依賴於衝突檢測),也不會阻塞,能夠採起一些補償機制,通常的策略就是反覆重試。很顯然,這種思想相比簡單粗暴利用鎖來保證同步要合理的多。

 ==================================================================

  1. 線程安全的map有哪些,concurrenthashmap是如何實現線程安全的(jdk1.8大不一樣)?

HashTable   ConcurrentHashMap 

鏈表改爲了紅黑樹,當鏈表中的結點達到一個閥值TREEIFY_THRESHOLD時,會將鏈表轉換爲紅黑樹,查詢效率提從原來的O(n),提升爲O(logn)

將每一個segment的分段鎖ReentrantLock改成CAS+Synchronized

併發環境下爲何使用ConcurrentHashMap

1. HashMap在高併發的環境下,執行put操做會致使HashMap的Entry鏈表造成環形數據結構,從而致使Entry的next節點始終不爲空,所以產生死循環獲取Entry

2. HashTable雖然是線程安全的,可是效率低下,當一個線程訪問HashTable的同步方法時,其餘線程若是也訪問HashTable的同步方法,那麼會進入阻塞或者輪訓狀態。

3. 在jdk1.6中ConcurrentHashMap使用鎖分段技術提升併發訪問效率。首先將數據分紅一段一段地存儲,而後給每一段數據配一個鎖,當一個線程佔用鎖訪問其中一段數據時,其餘段的數據也能被其餘線程訪問。然而在jdk1.8中的實現已經拋棄了Segment分段鎖機制,利用CAS+Synchronized來保證併發更新的安全,底層依然採用數組+鏈表+紅黑樹的存儲結構。 

 ==================================================================

鎖有哪幾種?

  •  公平鎖/非公平鎖
  • 可重入鎖
  • 獨享鎖/共享鎖
  • 互斥鎖/讀寫鎖
  • 樂觀鎖/悲觀鎖
  • 分段鎖
  • 偏向鎖/輕量級鎖/重量級鎖
  • 自旋鎖
  1. 公平鎖和非公平鎖。

公平鎖:公平鎖是指多個線程按照申請鎖的順序來獲取鎖。

非公平所:非公平鎖是指多個線程獲取鎖的順序並非按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會形成優先級反轉或者飢餓現象。
對於Java ReentrantLock而言,經過構造函數指定該鎖是不是公平鎖,默認是非公平鎖。非公平鎖的優勢在於吞吐量比公平鎖大。
對於Synchronized而言,也是一種非公平鎖。因爲其並不像ReentrantLock是經過AQS的來實現線程調度,因此並無任何辦法使其變成公平鎖。//默認是不公平鎖,傳入true爲公平鎖,不然爲非公平鎖

ReentrantLock reentrantLock =  new ReetrantLock();

共享鎖和獨享鎖

獨享鎖:一次只能被一個線程所訪問

共享鎖:線程能夠被多個線程所持有。

ReadWriteLock 讀鎖是共享鎖,寫鎖是獨享鎖。

對於Java ReentrantLock而言,其是獨享鎖。可是對於Lock的另外一個實現類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨享鎖。
讀鎖的共享鎖可保證併發讀是很是高效的,讀寫,寫讀 ,寫寫的過程是互斥的。
獨享鎖與共享鎖也是經過AQS來實現的,經過實現不一樣的方法,來實現獨享或者共享。
對於Synchronized而言,固然是獨享鎖。

樂觀鎖和悲觀鎖。 

樂觀鎖:對於一個數據的操做併發,是不會發生修改的。在更新數據的時候,會嘗試採用更新,不斷重入的方式,更新數據。

悲觀鎖:對於同一個數據的併發操做,是必定會發生修改的。所以對於同一個數據的併發操做,悲觀鎖採用加鎖的形式。悲觀鎖認爲,不加鎖的操做必定會出問題,

分段鎖

1.7及以前的concurrenthashmap。併發操做就是分段鎖,其思想就是讓鎖的粒度變小。

分段鎖實際上是一種鎖的設計,並非具體的一種鎖,對於ConcurrentHashMap而言,其併發的實現就是經過分段鎖的形式來實現高效的併發操做。
咱們以ConcurrentHashMap來講一下分段鎖的含義以及設計思想,ConcurrentHashMap中的分段鎖稱爲Segment,它即相似於HashMap(JDK7與JDK8中HashMap的實現)的結構,即內部擁有一個Entry數組,數組中的每一個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。
當須要put元素的時候,並非對整個hashmap進行加鎖,而是先經過hashcode來知道他要放在那一個分段中,而後對這個分段進行加鎖,因此當多線程put的時候,只要不是放在一個分段中,就實現了真正的並行的插入。
可是,在統計size的時候,可就是獲取hashmap全局信息的時候,就須要獲取全部的分段鎖才能統計。
分段鎖的設計目的是細化鎖的粒度,當操做不須要更新整個數組的時候,就僅僅針對數組中的一項進行加鎖操做。

偏向鎖/輕量級鎖/重量級鎖

偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。下降獲取鎖的代價 這三種鎖是指鎖的狀態,而且是針對Synchronized。在Java 5經過引入鎖升級的機制來實現高效Synchronized。這三種鎖的狀態是經過對象監視器在對象頭中的字段來代表的。
  偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。下降獲取鎖的代價。
   輕量級鎖 是指當鎖是偏向鎖的時候,被另外一個線程所訪問,偏向鎖就會升級爲輕量級鎖,其餘線程會經過自旋的形式嘗試獲取鎖,不會阻塞,提升性能。
重量級鎖是指當鎖爲輕量級鎖的時候,另外一個線程雖然是自旋,但自旋不會一直持續下去,當自旋必定次數的時候,尚未獲取到鎖,就會進入阻塞,該鎖膨脹爲重量級鎖。重量級鎖會讓其餘申請的線程進入阻塞,性能下降。

6自旋鎖

在Java中,自旋鎖是指嘗試獲取鎖的線程不會當即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減小線程上下文切換的消耗,缺點是循環會消耗CPU。

不少synchronized裏面的代碼只是一些很簡單的代碼,執行時間很是快,此時等待的線程都加鎖多是一種不太值得的操做,由於線程阻塞涉及到用戶態和內核態切換的問題。既然synchronized裏面的代碼執行得很是快,不妨讓等待鎖的線程不要被阻塞,而是在synchronized的邊界作忙循環,這就是自旋。若是作了屢次忙循環發現尚未得到鎖,再阻塞,這樣多是一種更好的策略。

 

7可重入鎖

可重入鎖又名遞歸鎖,是指在同一個線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖。說的有點抽象,下面會有一個代碼的示例。
對於Java ReentrantLock而言, 他的名字就能夠看出是一個可重入鎖,其名字是Re entrant Lock從新進入鎖。
對於Synchronized而言,也是一個可重入鎖。可重入鎖的一個好處是可必定程度避免死鎖。

synchronized void setA() throws Exception{

    Thread.sleep(1000);

    setB();

}

synchronized void setB() throws Exception{

    Thread.sleep(1000);

}

上面的代碼就是一個可重入鎖的一個特色,若是不是可重入鎖的話,setB可能不會被當前線程執行,可能形成死鎖。

 ==================================================================

公平鎖,讀寫鎖等如何實現?

 ==================================================================

10 synchronize能加在哪些地方?什麼區別?

  1. synchronized 在方法上,全部這個類的加了 synchronized 的方法,在執行時,會得到一個該類的惟一的同步鎖,當這個鎖被佔用時,其餘的加了 synchronized 的方法就必須等待
     2.加在對象上的話,就是以這個對象爲鎖,其餘也以這個對象爲鎖的代碼段,在這個鎖被佔用時,就必須等待

==================================================================

11 原子數據對象的原理?

以AtomicInteger爲例 AtomicInteger是對int類型的一個封裝,提供原子性的訪問和更新操做,其原子性操做的實現是基於CAS(compare-and-swap)技術。

所謂CAS,表現爲一組指令,當利用CAS執行試圖進行一些更新操做時。會首先比較當前數值,若是數值未變,表明沒有其它線程進行併發修改,則成功更新。若是數值改變,則可能出現不一樣的選擇,要麼進行重試,要麼就返回是否成功。也就是所謂的「樂觀鎖」。

從AtomicInteger的內部屬性能夠看出,它依賴於Unsafe提供的一些底層能力,進行底層操做;以volatile的value字段,記錄數值,以保證可見性。

private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();

private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");

private volatile int value;

具體的原子操做細節,能夠參考任意一個原子更新方法,好比下面的getAndIncrement。Unsafe會利用value字段的內存地址偏移,直接完成操做。

public final int getAndIncrement() {

  return U.getAndAddInt(this, VALUE, 1);

}

由於getAndIncrement須要返回數值,因此須要添加失敗重試邏輯。

public final int getAndAddInt(Object o, long offset, int delta) {

  int v;

  do {

  v = getIntVolatile(o, offset);

  } while (!weakCompareAndSetInt(o, offset, v, v + delta));

  return v;

}

而相似compareAndSet這種返回boolean類型的函數,由於其返回值表現的就是是否成功與否,因此不須要重試。

public final boolean compareAndSet(int expectedValue, int newValue)

CAS是Java併發中所謂lock-free機制的基礎。

==================================================================

12 reentrantlock相關知識,condition如何使用?(很重要的知識點,強烈推薦閱讀ArrayBlockingQueue源碼,教科書般)

ReentrantLock

ReentrantLock能夠等同於synchronized使用。是一個可重入的互斥鎖,它具備與使用synchronized方法和語句所訪問的隱式監視器鎖相同的一些基本行爲和語義,但功能更強大。

ReentrantLock 類實現了Lock ,它擁有與 synchronized 相同的併發性和內存語義,可是添加了相似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用狀況下更佳的性能。(換句話說,當許多線程都想訪問共享資源時,JVM 能夠花更少的時候來調度線程,把更多時間用在執行線程上。

Condition

線程之間的通訊。Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成大相徑庭的對象,以便經過將這些對象與任意 Lock 實現組合使用。

==================================================================

13 CyclicBarrier和CountDownLatch的區別

兩個看上去有點像的類,都在java.util.concurrent下,均可以用來表示代碼運行到某個點上,兩者的區別在於:

(1)CyclicBarrier的某個線程運行到某個點上以後,該線程即中止運行,直到全部的線程都到達了這個點,全部線程才從新運行;CountDownLatch則不是,某線程運行到某個點上以後,只是給某個數值-1而已,該線程繼續運行

(2)CyclicBarrier只能喚起一個任務,CountDownLatch能夠喚起多個任務

(3)CyclicBarrier可重用,CountDownLatch不可重用,計數值爲0該CountDownLatch就不可再用了

==================================================================

14 volatile的相關知識(內存屏障,重排)

==================================================================

15 ThreadLocal原理和使用?(超級有用的知識點,工做中使用不少,讓代碼漂亮不少)

簡單說ThreadLocal就是一種以空間換時間的作法,在每一個Thread裏面維護了一個以開地址法實現的ThreadLocal.ThreadLocalMap,把數據進行隔離,數據不共享,天然就沒有線程安全方面的問題了

==================================================================

1六、爲何要使用線程池

避免頻繁地建立和銷燬線程,達到線程對象的重用。另外,使用線程池還能夠根據項目靈活地控制併發的數目。

==================================================================

15 多個線程同步等待?(CountDownLatch,CyclicBarrier,Semaphore信號量不少語言都有,實際上使用不是不少,線程池就能夠實現大部分等待功能)

==================================================================

16線程池?(種類,重要的方法,這個通常是使用層面,簡單)

==================================================================

17、Java中如何獲取到線程dump文件

死循環、死鎖、阻塞、頁面打開慢等問題,打線程dump是最好的解決問題的途徑。所謂線程dump也就是線程堆棧,獲取到線程堆棧有兩步:

(1)獲取到線程的pid,能夠經過使用jps命令,在Linux環境下還可使用ps -ef | grep java

(2)打印線程堆棧,能夠經過使用jstack pid命令,在Linux環境下還可使用kill -3 pid

另外提一點,Thread類提供了一個getStackTrace()方法也能夠用於獲取線程堆棧。這是一個實例方法,所以此方法是和具體線程實例綁定的,每次獲取獲取到的是具體某個線程當前運行的堆棧

==================================================================

18、一個線程若是出現了運行時異常會怎麼樣

若是這個異常沒有被捕獲的話,這個線程就中止執行了。另外重要的一點是:若是這個線程持有某個某個對象的監視器,那麼這個對象監視器會被當即釋放

==================================================================

19、如何在兩個線程之間共享數據(線程同步)

經過在線程之間共享對象就能夠了,而後經過wait/notify/notifyAll、await/signal/signalAll進行喚起和等待,比方說阻塞隊列BlockingQueue就是爲線程之間共享數據而設計的

==================================================================

20、ReadWriteLock是什麼

首先明確一下,不是說ReentrantLock很差,只是ReentrantLock某些時候有侷限。若是使用ReentrantLock,可能自己是爲了防止線程A在寫數據、線程B在讀數據形成的數據不一致,但這樣,若是線程C在讀數據、線程D也在讀數據,讀數據是不會改變數據的,沒有必要加鎖,可是仍是加鎖了,下降了程序的性能。

由於這個,才誕生了讀寫鎖ReadWriteLock。ReadWriteLock是一個讀寫鎖接口,ReentrantReadWriteLock是ReadWriteLock接口的一個具體實現,實現了讀寫的分離,讀鎖是共享的,寫鎖是獨佔的,讀和讀之間不會互斥,讀和寫、寫和讀、寫和寫之間纔會互斥,提高了讀寫的性能。

==================================================================

2一、FutureTask是什麼

這個其實前面有提到過,FutureTask表示一個異步運算的任務。FutureTask裏面能夠傳入一個Callable的具體實現類,能夠對這個異步運算的任務的結果進行等待獲取、判斷是否已經完成、取消任務等操做。固然,因爲FutureTask也是Runnable接口的實現類,因此FutureTask也能夠放入線程池中。

==================================================================

2二、Linux環境下如何查找哪一個線程使用CPU最長

這是一個比較偏實踐的問題,這種問題我以爲挺有意義的。能夠這麼作:

(1)獲取項目的pid,jps或者ps -ef | grep java,這個前面有講過

(2)top -H -p pid,順序不能改變

這樣就能夠打印出當前的項目,每條線程佔用CPU時間的百分比。注意這裏打出的是LWP,也就是操做系統原生線程的線程號,我筆記本山沒有部署Linux環境下的Java工程,所以沒有辦法截圖演示,網友朋友們若是公司是使用Linux環境部署項目的話,能夠嘗試一下。

使用"top -H -p pid"+"jps pid"能夠很容易地找到某條佔用CPU高的線程的線程堆棧,從而定位佔用CPU高的緣由,通常是由於不當的代碼操做致使了死循環。

最後提一點,"top -H -p pid"打出來的LWP是十進制的,"jps pid"打出來的本地線程號是十六進制的,轉換一下,就能定位到佔用CPU高的線程的當前線程堆棧了。

 ==================================================================

23、怎麼喚醒一個阻塞的線程

若是線程是由於調用了wait()、sleep()或者join()方法而致使的阻塞,能夠中斷線程,而且經過拋出InterruptedException來喚醒它;若是線程遇到了IO阻塞,無能爲力,由於IO是操做系統實現的,Java代碼並無辦法直接接觸到操做系統。

==================================================================

24、若是你提交任務時,線程池隊列已滿,這時會發生什麼

這裏區分一下:

1若是使用的是無界隊列LinkedBlockingQueue,也就是無界隊列的話,不要緊,繼續添加任務到阻塞隊列中等待執行,由於LinkedBlockingQueue能夠近乎認爲是一個無窮大的隊列,能夠無限存聽任務

2 若是使用的是有界隊列好比ArrayBlockingQueue,任務首先會被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿了,會根據maximumPoolSize的值增長線程數量,若是增長了線程數量仍是處理不過來,ArrayBlockingQueue繼續滿,那麼則會使用拒絕策略RejectedExecutionHandler處理滿了的任務,默認是AbortPolicy

==================================================================

26、什麼是CAS什麼是AQS

CAS,全稱爲Compare and Swap,即比較-替換。假設有三個操做數:內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,纔會將內存值修改成B並返回true,不然什麼都不作並返回false。固然CAS必定要volatile變量配合,這樣才能保證每次拿到的變量是主內存中最新的那個值,不然舊的預期值A對某條線程來講,永遠是一個不會變的值A,只要某次CAS操做失敗,永遠都不可能成功。

簡單說一下AQS,AQS全稱爲AbstractQueuedSychronizer,翻譯過來應該是抽象隊列同步器。

若是說java.util.concurrent的基礎是CAS的話,那麼AQS就是整個Java併發包的核心了,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS實際上以雙向隊列的形式鏈接全部的Entry,比方說ReentrantLock,全部等待的線程都被放在一個Entry中並連成雙向隊列,前面一個線程使用ReentrantLock好了,則雙向隊列實際上的第一個Entry開始運行。

AQS定義了對雙向隊列全部的操做,而只開放了tryLock和tryRelease方法給開發者使用,開發者能夠根據本身的實現重寫tryLock和tryRelease方法,以實現本身的併發功能。

==================================================================

27、單例模式的線程安全性

老生常談的問題了,首先要說的是單例模式的線程安全意味着:某個類的實例在多線程環境下只會被建立一次出來。單例模式有不少種的寫法,我總結一下:

(1)餓漢式單例模式的寫法:線程安全

(2)懶漢式單例模式的寫法:非線程安全

(3)雙檢鎖單例模式的寫法:線程安全

==================================================================

28、Semaphore有什麼做用

Semaphore就是一個信號量,它的做用是限制某段代碼塊的併發數。Semaphore有一個構造函數,能夠傳入一個int型整數n,表示某段代碼最多隻有n個線程能夠訪問,若是超出了n,那麼請等待,等到某個線程執行完畢這段代碼塊,下一個線程再進入。由此能夠看出若是Semaphore構造函數中傳入的int型整數n=1,至關於變成了一個synchronized了。

==================================================================

29、Hashtable的size()方法中明明只有一條語句"return count",爲何還要作同步?

主要緣由有兩點:

(1)同一時間只能有一條線程執行固定類的同步方法,可是對於類的非同步方法,能夠多條線程同時訪問。因此,這樣就有問題了,可能線程A在執行Hashtable的put方法添加數據,線程B則能夠正常調用size()方法讀取Hashtable中當前元素的個數,那讀取到的值可能不是最新的,可能線程A添加了完了數據,可是沒有對size++,線程B就已經讀取size了,那麼對於線程B來講讀取到的size必定是不許確的。而給size()方法加了同步以後,意味着線程B調用size()方法只有在線程A調用put方法完畢以後才能夠調用,這樣就保證了線程安全性

(2)CPU執行代碼,執行的不是Java代碼,這點很關鍵,必定得記住。Java代碼最終是被翻譯成機器碼執行的,機器碼纔是真正能夠和硬件電路交互的代碼。即便你看到Java代碼只有一行,甚至你看到Java代碼編譯以後生成的字節碼也只有一行,也不意味着對於底層來講這句語句的操做只有一個。一句"return count"假設被翻譯成了三句彙編語句執行,一句彙編語句和其機器碼作對應,徹底可能執行完第一句,線程就切換了。

==================================================================

30、高併發、任務執行時間短的業務怎樣使用線程池?併發不高、任務執行時間長的業務怎樣使用線程池?併發高、業務執行時間長的業務怎樣使用線程池?

這是我在併發編程網上看到的一個問題,把這個問題放在最後一個,但願每一個人都能看到而且思考一下,由於這個問題很是好、很是實際、很是專業。關於這個問題,我的見解是:

(1)高併發、任務執行時間短的業務,線程池線程數能夠設置爲CPU核數+1,減小線程上下文的切換

(2)併發不高、任務執行時間長的業務要區分開看:

a)假如是業務時間長集中在IO操做上,也就是IO密集型的任務,由於IO操做並不佔用CPU,因此不要讓全部的CPU閒下來,能夠加大線程池中的線程數目,讓CPU處理更多的業務

b)假如是業務時間長集中在計算操做上,也就是計算密集型任務,這個就沒辦法了,和(1)同樣吧,線程池中的線程數設置得少一些,減小線程上下文的切換

(3)併發高、業務執行時間長,解決這種類型任務的關鍵不在於線程池而在於總體架構的設計,看看這些業務裏面某些數據是否能作緩存是第一步,增長服務器是第二步,至於線程池的設置,設置參考(2)。最後,業務執行時間長的問題,也可能須要分析一下,看看能不能使用中間件對任務進行拆分和解耦。

==================================================================

31、怎麼檢測一個線程是否持有對象監視器

我也是在網上看到一道多線程面試題才知道有方法能夠判斷某個線程是否持有對象監視器:Thread類提供了一個holdsLock(Object obj)方法,當且僅當對象obj的監視器被某條線程持有的時候纔會返回true,注意這是一個static方法,這意味着"某條線程"指的是當前線程

==================================================================

32、ConcurrentHashMap的併發度是什麼

ConcurrentHashMap的併發度就是segment的大小,默認爲16,這意味着最多同時能夠有16條線程操做ConcurrentHashMap,這也是ConcurrentHashMap對Hashtable的最大優點,任何狀況下,Hashtable能同時有兩條線程獲取Hashtable中的數據嗎?

=================================================================

相關文章
相關標籤/搜索