史上最強多線程面試44題和答案:線程鎖+線程池+線程同步等

一、併發編程三要素?java

1)原子性算法

原子性指的是一個或者多個操做,要麼所有執行而且在執行的過程當中不被其餘操做打斷,要麼就所有都不執行。編程

2)可見性緩存

可見性指多個線程操做一個共享變量時,其中一個線程對變量進行修改後,其餘線程能夠當即看到修改的結果。安全

實現可見性的方法:多線程

synchronized或者Lock:保證同一個時刻只有一個線程獲取鎖執行代碼,鎖釋放以前把最新的值刷新到主內存,實現可見性。併發

3)有序性框架

有序性,即程序的執行順序按照代碼的前後順序來執行。異步


二、多線程的價值?分佈式

1)發揮多核CPU的優點

多線程,能夠真正發揮出多核CPU的優點來,達到充分利用CPU的目的,採用多線程的方式去同時完成幾件事情而不互相干擾。

2)防止阻塞

從程序運行效率的角度來看,單核CPU不但不會發揮出多線程的優點,反而會由於在單核CPU上運行多線程致使線程上下文的切換,而下降程序總體的效率。可是單核CPU咱們仍是要應用多線程,就是爲了防止阻塞。試想,若是單核CPU使用單線程,那麼只要這個線程阻塞了,比方說遠程讀取某個數據吧,對端遲遲未返回又沒有設置超時時間,那麼你的整個程序在數據返回回來以前就中止運行了。多線程能夠防止這個問題,多條線程同時運行,哪怕一條線程的代碼執行讀取數據阻塞,也不會影響其它任務的執行。

3)便於建模

這是另一個沒有這麼明顯的優勢了。假設有一個大的任務A,單線程編程,那麼就要考慮不少,創建整個程序模型比較麻煩。可是若是把這個大的任務A分解成幾個小任務,任務B、任務C、任務D,分別創建程序模型,並經過多線程分別運行這幾個任務,那就簡單不少了。


三、建立線程的有哪些方式?

1)繼承Thread類建立線程類

2)經過Runnable接口建立線程類

3)經過Callable和Future建立線程


4.建立線程的三種方式的對比?

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對象能夠了解任務執行狀況,可取消任務的執行,還可獲取執行結果。

五、線程的狀態流轉圖

線程的生命週期及五種基本狀態:

 

 

 

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()方法,該線程結束生命週期。


6.什麼是線程池? 有哪幾種建立方式?

線程池就是提早建立若干個線程,若是有任務須要處理,線程池裏的線程就會處理任務,處理完以後線程並不會被銷燬,而是等待下一個任務。因爲建立和銷燬線程都是消耗系統資源的,因此當你想要頻繁的建立和銷燬線程的時候就能夠考慮使用線程池來提高系統的性能。

java 提供了一個 java.util.concurrent.Executor接口的實現用於建立線程池。

四種線程池的建立:

(1)newCachedThreadPool建立一個可緩存線程池

(2)newFixedThreadPool 建立一個定長線程池,可控制線程最大併發數。

(3)newScheduledThreadPool 建立一個定長線程池,支持定時及週期性任務執行。

(4)newSingleThreadExecutor 建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務。


7.線程池的優勢?

1)重用存在的線程,減小對象建立銷燬的開銷。

2)可有效的控制最大併發線程數,提升系統資源的使用率,同時避免過多資源競爭,避免堵塞。

3)提供定時執行、按期執行、單線程、併發數控制等功能。


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

同步集合類:

  • Vector
  • Stack
  • HashTable
  • Collections.synchronized方法生成

併發集合類:

  • ConcurrentHashMap
  • CopyOnWriteArrayList
  • CopyOnWriteArraySet等

9.同步集合與併發集合的區別

同步集合與併發集合都爲多線程和併發提供了合適的線程安全的集合,不過併發集合的可擴展性更高。同步集合比並發集合會慢得多,主要緣由是鎖,同步集合會對整個May或List加鎖,而併發集合例如ConcurrentHashMap,

把整個Map 劃分紅幾個片斷,只對相關的幾個片斷上鎖,同時容許多線程訪問其餘未上鎖的片斷(JDK1.8版本底層加入了紅黑樹)。


10.經常使用的併發工具類有哪些?

  • CountDownLatch
  • CyclicBarrier
  • Semaphore
  • Exchanger

11.CyclicBarrier和CountDownLatch的應用場景?

CountDownLatch : 一個線程(或者多個), 等待另外N個線程完成某個事情以後才能執行。 CyclicBarrier : N個線程相互等待,任何一個線程完成以前,全部的線程都必須等待。

CountDownLatch的使用場景:

在一些應用場合中,須要等待某個條件達到要求後才能作後面的事情;同時當線程都完成後也會觸發事件,以便進行後面的操做, 這個時候就可使用CountDownLatch。

CyclicBarrier 使用場景

CyclicBarrier能夠用於多線程計算數據,最後合併計算結果的應用場景。


12.CyclicBarrier和CountDownLatch的區別

1)CountDownLatch簡單的說就是一個線程等待,直到他所等待的其餘線程都執行完成而且調用countDown()方法發出通知後,當前線程才能夠繼續執行。

2)cyclicBarrier是全部線程都進行等待,直到全部線程都準備好進入await()方法以後,全部線程同時開始執行!

3)CountDownLatch的計數器只能使用一次。而CyclicBarrier的計數器可使用reset() 方法重置。因此CyclicBarrier能處理更爲複雜的業務場景,好比若是計算髮生錯誤,能夠重置計數器,並讓線程們從新執行一次。

4)CyclicBarrier還提供其餘有用的方法,好比getNumberWaiting方法能夠得到CyclicBarrier阻塞的線程數量。isBroken方法用來知道阻塞的線程是否被中斷。若是被中斷返回true,不然返回false。

13.synchronized的做用?

在Java中,synchronized關鍵字是用來控制線程同步的,就是在多線程的環境下,控制synchronized代碼段不被多個線程同時執行。

synchronized既能夠加在一段代碼上,也能夠加在方法上。


14.volatile關鍵字的做用

對於可見性,Java提供了volatile關鍵字來保證可見性。

當一個共享變量被volatile修飾時,它會保證修改的值會當即被更新到主存,當有其餘線程須要讀取時,它會去內存中讀取新值。

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


15.什麼是CAS

CAS是compare and swap的縮寫,即咱們所說的比較交換。

cas是一種基於鎖的操做,並且是樂觀鎖。在java中鎖分爲樂觀鎖和悲觀鎖。悲觀鎖是將資源鎖住,等一個以前得到鎖的線程釋放鎖以後,下一個線程才能夠訪問。而樂觀鎖採起了一種寬泛的態度,經過某種方式不加鎖來處理資源,好比經過給記錄加version來獲取數據,性能較悲觀鎖有很大的提升。

CAS

操做包含三個操做數 ——

內存位置(V)、預期原值(A)和新值(B)。若是內存地址裏面的值和A的值是同樣的,那麼就將內存裏面的值更新成B。CAS是經過無限循環來獲取數據的,若果在第一輪循環中,a線程獲取地址裏面的值被b線程修改了,那麼a線程須要自旋,到下次循環纔有可能機會執行。

java.util.concurrent.atomic 包下的類大可能是使用CAS操做來實現的( AtomicInteger,AtomicBoolean,AtomicLong)。


16. CAS的問題

1)CAS容易形成ABA問題。一個線程a將數值改爲了b,接着又改爲了a,此時CAS認爲是沒有變化,實際上是已經變化過了,而這個問題的解決方案可使用版本號標識,每操做一次version加1。在java5中,已經提供了AtomicStampedReference來解決問題。

2) 不能保證代碼塊的原子性

CAS機制所保證的知識一個變量的原子性操做,而不能保證整個代碼塊的原子性。好比須要保證3個變量共同進行原子性的更新,就不得不使用synchronized了。

3)CAS形成CPU利用率增長。以前說過了CAS裏面是一個循環判斷的過程,若是線程一直沒有獲取到狀態,cpu資源會一直被佔用。


17.什麼是Future?

在併發編程中,咱們常常用到非阻塞的模型,在以前的多線程的三種實現中,無論是繼承thread類仍是實現runnable接口,都沒法保證獲取到以前的執行結果。經過實現Callback接口,並用Future能夠來接收多線程的執行結果。

Future表示一個可能尚未完成的異步任務的結果,針對這個結果能夠添加Callback以便在任務執行成功或失敗後做出相應的操做。


18.什麼是AQS

AQS是AbustactQueuedSynchronizer的簡稱,它是一個Java提升的底層同步工具類,用一個int類型的變量表示同步狀態,並提供了一系列的CAS操做來管理這個同步狀態。

AQS是一個用來構建鎖和同步器的框架,使用AQS能簡單且高效地構造出應用普遍的大量的同步器,好比咱們提到的ReentrantLock,Semaphore,其餘的諸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基於AQS的。

AQS支持兩種同步方式:

1.獨佔式

2.共享式

這樣方便使用者實現不一樣類型的同步組件,獨佔式如ReentrantLock,共享式如Semaphore,CountDownLatch,組合式的如ReentrantReadWriteLock。總之,AQS爲使用提供了底層支撐,如何組裝實現,使用者能夠自由發揮。


19.ReadWriteLock是什麼

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

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


20.FutureTask是什麼

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


21.synchronized和ReentrantLock的區別

synchronized是和if、else、for、while同樣的關鍵字,ReentrantLock是類,這是兩者的本質區別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,能夠被繼承、能夠有方法、能夠有各類各樣的類變量,ReentrantLock比synchronized的擴展性體如今幾點上:

(1)ReentrantLock能夠對獲取鎖的等待時間進行設置,這樣就避免了死鎖

(2)ReentrantLock能夠獲取各類鎖的信息

(3)ReentrantLock能夠靈活地實現多路通知

另外,兩者的鎖機制其實也是不同的。ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操做的應該是對象頭中mark word,這點我不能肯定。


22.什麼是樂觀鎖和悲觀鎖

(1)樂觀鎖:就像它的名字同樣,對於併發間操做產生的線程安全問題持樂觀狀態,樂觀鎖認爲競爭不老是會發生,所以它不須要持有鎖,將比較-替換這兩個動做做爲一個原子操做嘗試去修改內存中的變量,若是失敗則表示發生衝突,那麼就應該有相應的重試邏輯。

(2)悲觀鎖:仍是像它的名字同樣,對於併發間操做產生的線程安全問題持悲觀狀態,悲觀鎖認爲競爭老是會發生,所以每次對某資源進行操做時,都會持有一個獨佔的鎖,就像synchronized,無論三七二十一,直接上了鎖就操做資源了。


23.線程B怎麼知道線程A修改了變量

  • volatile修飾變量
  • synchronized修飾修改變量的方法
  • wait/notify
  • while輪詢

24.synchronized、volatile、CAS比較

  • synchronized是悲觀鎖,屬於搶佔式,會引發其餘線程阻塞。
  • volatile提供多線程共享變量可見性和禁止指令重排序優化。
  • CAS是基於衝突檢測的樂觀鎖(非阻塞)

25.sleep方法和wait方法有什麼區別?

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


26.ThreadLocal是什麼?有什麼用?

ThreadLocal是一個本地線程副本變量工具類。主要用於將私有線程和該線程存放的副本對象作一個映射,各個線程之間的變量互不干擾,在高併發場景下,能夠實現無狀態的調用,特別適用於各個線程依賴不通的變量值完成操做的場景。

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


27.爲何wait()方法和notify()/notifyAll()方法要在同步塊中被調用

這是JDK強制的,wait()方法和notify()/notifyAll()方法在調用前都必須先得到對象的鎖


28.多線程同步有哪幾種方法?

Synchronized關鍵字,Lock鎖實現,分佈式鎖等。


29.線程的調度策略

線程調度器選擇優先級最高的線程運行,可是,若是發生如下狀況,就會終止線程的運行:

(1)線程體中調用了yield方法讓出了對cpu的佔用權利

(2)線程體中調用了sleep方法使線程進入睡眠狀態

(3)線程因爲IO操做受到阻塞

(4)另一個更高優先級線程出現

(5)在支持時間片的系統中,該線程的時間片用完

30.ConcurrentHashMap的併發度是什麼

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


31.Java死鎖以及如何避免?

Java中的死鎖是一種編程狀況,其中兩個或多個線程被永久阻塞,Java死鎖狀況出現至少兩個線程和兩個或更多資源。

Java發生死鎖的根本緣由是:在申請鎖時發生了交叉閉環申請。

死鎖的緣由

1)是多個線程涉及到多個鎖,這些鎖存在着交叉,因此可能會致使了一個鎖依賴的閉環。

例如:線程在得到了鎖A而且沒有釋放的狀況下去申請鎖B,這時,另外一個線程已經得到了鎖B,在釋放鎖B以前又要先得到鎖A,所以閉環發生,陷入死鎖循環。

2)默認的鎖申請操做是阻塞的。

因此要避免死鎖,就要在一遇到多個對象鎖交叉的狀況,就要仔細審查這幾個對象的類中的全部方法,是否存在着致使鎖依賴的環路的可能性。 總之是儘可能避免在一個同步方法中調用其它對象的延時方法和同步方法。


32.怎麼喚醒一個阻塞的線程

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


33.不可變對象對多線程有什麼幫助

前面有提到過的一個問題,不可變對象保證了對象的內存可見性,對不可變對象的讀取不須要進行額外的同步手段,提高了代碼執行效率。


34.什麼是多線程的上下文切換

多線程的上下文切換是指CPU控制權由一個已經正在運行的線程切換到另一個就緒並等待獲取CPU執行權的線程的過程。


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

這裏區分一下:

  1. 若是使用的是無界隊列LinkedBlockingQueue,也就是無界隊列的話,不要緊,繼續添加任務到阻塞隊列中等待執行,由於LinkedBlockingQueue能夠近乎認爲是一個無窮大的隊列,能夠無限存聽任務
  2. 若是使用的是有界隊列好比ArrayBlockingQueue,任務首先會被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿了,會根據maximumPoolSize的值增長線程數量,若是增長了線程數量仍是處理不過來,ArrayBlockingQueue繼續滿,那麼則會使用拒絕策略RejectedExecutionHandler處理滿了的任務,默認是AbortPolicy

36.Java中用到的線程調度算法是什麼

搶佔式。一個線程用完CPU以後,操做系統會根據線程優先級、線程飢餓狀況等數據算出一個總的優先級並分配下一個時間片給某個線程執行。


37.什麼是線程調度器(Thread Scheduler)和時間分片(Time Slicing)?

線程調度器是一個操做系統服務,它負責爲Runnable狀態的線程分配CPU時間。一旦咱們建立一個線程並啓動它,它的執行便依賴於線程調度器的實現。時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。分配CPU時間能夠基於線程優先級或者線程等待的時間。線程調度並不受到Java虛擬機控制,因此由應用程序來控制它是更好的選擇(也就是說不要讓你的程序依賴於線程的優先級)。


38.Java Concurrency API中的Lock接口(Lock interface)是什麼?對比同步它有什麼優點?

Lock接口比同步方法和同步塊提供了更具擴展性的鎖操做。他們容許更靈活的結構,能夠具備徹底不一樣的性質,而且能夠支持多個相關類的條件對象。

它的優點有:

  • 可使鎖更公平
  • 可使線程在等待鎖的時候響應中斷
  • 可讓線程嘗試獲取鎖,並在沒法獲取鎖的時候當即返回或者等待一段時間
  • 能夠在不一樣的範圍,以不一樣的順序獲取和釋放鎖

39.單例模式的線程安全性

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

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

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

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


40.Semaphore有什麼做用

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

41.Executors類是什麼?

Executors爲Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable類提供了一些工具方法。

Executors能夠用於方便的建立線程池


42.線程類的構造方法、靜態塊是被哪一個線程調用的

這是一個很是刁鑽和狡猾的問題。請記住:線程類的構造方法、靜態塊是被new這個線程類所在的線程所調用的,而run方法裏面的代碼纔是被線程自身所調用的。

若是說上面的說法讓你感到困惑,那麼我舉個例子,假設Thread2中new了Thread1,main函數中new了Thread2,那麼:

(1)Thread2的構造方法、靜態塊是main線程調用的,Thread2的run()方法是Thread2本身調用的

(2)Thread1的構造方法、靜態塊是Thread2調用的,Thread1的run()方法是Thread1本身調用的

43.同步方法和同步塊,哪一個是更好的選擇

同步塊,這意味着同步塊以外的代碼是異步執行的,這比同步整個方法更提高代碼的效率。請知道一條原則:同步的範圍越小越好。


44.Java線程數過多會形成什麼異常?

1)線程的生命週期開銷很是高

2)消耗過多的CPU資源

若是可運行的線程數量多於可用處理器的數量,那麼有線程將會被閒置。大量空閒的線程會佔用許多內存,給垃圾回收器帶來壓力,並且大量的線程在競爭CPU資源時還將產生其餘性能的開銷。

3)下降穩定性

JVM在可建立線程的數量上存在一個限制,這個限制值將隨着平臺的不一樣而不一樣,而且承受着多個因素制約,包括JVM的啓動參數、Thread構造函數中請求棧的大小,以及底層操做系統對線程的限制等。若是破壞了這些限制,那麼可能拋出OutOfMemoryError異常。

相關文章
相關標籤/搜索