1. 程式人生 > >JAVA執行緒知識點

JAVA執行緒知識點

java執行緒知識點大全

1、 什麼是執行緒?

  • 執行緒是作業系統能夠進行運算的最小單位,他包含在實際的運作單位裡面,是程序中的實際運作單位。
  • 程式設計師可以通過它進行多處理器程式設計,你可以使用多執行緒對運算密集型任務提速。比如,如果一個執行緒完成一個任務要100毫秒,那麼用十個執行緒完成改任務只需10毫秒。Java在語言層面對多執行緒提供了卓越的支援,它也是一個很好的賣點
  • 它可與同屬一個程序的其它執行緒共享程序所擁有的全部資源。一個執行緒可以建立和撤消另一個執行緒,同一程序中的多個執行緒之間可以併發執行。執行緒也有就緒、阻塞和執行三種基本狀態。我們通過多執行緒程式設計,能更高效的提高系統內多個程式間併發執行的程度,從而顯著提高系統資源的利用率和吞吐量。

2、程序排程演算法

  • 實時系統:FIFO(First Input First Output,先進先出演算法),SJF(Shortest Job First,最短作業優先演算法),SRTF(Shortest Remaining Time First,最短剩餘時間優先演算法)。
  • 互式系統:RR(Round Robin,時間片輪轉演算法),HPF(Highest Priority First,最高優先順序演算法),多級佇列,最短程序優先,保證排程,彩票排程,公平分享排程。

3、執行緒和程序有什麼區別?

  • 執行緒是程序的子集,一個程序可以有很多執行緒,每條執行緒並行執行不同的任務。不同的程序使用不同的記憶體空間,而所有的執行緒共享一片相同的記憶體空間。別把它和棧記憶體搞混,每個執行緒都擁有單獨的棧記憶體用來儲存本地資料
  • 通訊:不同程序之間通過IPC(程序間通訊)介面進行通訊。同一程序的執行緒間可以直接讀寫程序資料段(如全域性變數)來進行通訊——需要程序同步和互斥手段的輔助,以保證資料的一致性。
  • 排程和切換:執行緒上下文切換比程序上下文切換要快得多。

4、多執行緒程式設計的好處是什麼?

  • 在多執行緒程式中,多個執行緒被併發的執行以提高程式的效率,CPU不會因為某個執行緒需要等待資源而進入空閒狀態(提高CPU的利用率)。
  • 多個執行緒共享堆記憶體(heap memory),因此建立多個執行緒去執行一些任務會比建立多個程序更好。舉個例子,Servlets比CGI更好,是因為Servlets支援多執行緒而CGI不支援。

5、如何在java中實現多執行緒

  • 在語言層面有兩種方式。可以繼承java.lang.Thread執行緒類,但是它需要呼叫java.lang.Runnable介面來執行。由於執行緒類本身就是呼叫的Runnable介面,所以你可以繼承java.lang.Thread類或者直接呼叫Runnable介面來重寫run()方法實現執行緒。
  • 還可以實現callable介面,和實現 Runnable介面一樣。
  • 那麼選擇哪個更好?
    • 由於Java不支援類的多重繼承,但允許呼叫多個介面。因此我們建議呼叫Runnable介面來建立執行緒.

6、Thread 類中的start() 和 run() 方法有什麼區別?

  • start()方法被用來啟動新建立的執行緒,而且start()內部呼叫了run()方法,這和直接呼叫run()方法的效果不一樣
    • 首先,start方法內部會呼叫run方法。
    • start與run方法的主要區別在於當程式呼叫start方法一個新執行緒將會被建立,並且在run方法中的程式碼將會在新執行緒上執行。
    • 然而在你直接呼叫run方法的時候,程式並不會建立新執行緒,run方法內部的程式碼將在當前執行緒上執行。大多數情況下呼叫run方法是一個bug或者變成失誤。因為呼叫者的初衷是呼叫start方法去開啟一個新的執行緒,這個錯誤可以被很多靜態程式碼覆蓋工具檢測出來,比如與fingbugs. 如果你想要執行需要消耗大量時間的任務,你最好使用start方法,否則在你呼叫run方法的時候,你的主執行緒將會被卡住。
    • 還有一個區別在於,一但一個執行緒被啟動,你不能重複呼叫該thread物件的start方法,呼叫已經啟動執行緒的start方法將會報IllegalStateException異常, 而你卻可以重複呼叫run方法。

7、notify()和notifyAll()有什麼區別?

  • 兩者最大的區別:
    • notifyAll使所有原來在該物件上等待被notify的執行緒統統退出wait的狀態,變成等待該物件上的鎖,一旦該物件被解鎖,他們就會去競爭。
    • notify他只是選擇一個wait狀態執行緒進行通知,並使它獲得該物件上的鎖,但不驚動其他同樣在等待被該物件notify的執行緒們,當第一個執行緒執行完畢以後釋放物件上的鎖,此時如果該物件沒有再次使用notify語句,即便該物件已經空閒,其他wait狀態等待的執行緒由於沒有得到該物件的通知,繼續處在wait狀態,直到這個物件發出一個notify或notifyAll,它們等待的是被notify或notifyAll,而不是鎖。
  • notify()和notifyAll()都是Object物件用於通知處在等待該物件的執行緒的方法。
  • void notify(): 喚醒一個正在等待該物件的執行緒。
  • void notifyAll(): 喚醒所有正在等待該物件的執行緒。

8、請說出與執行緒同步以及執行緒排程相關的方法。

  • wait():使一個執行緒處於等待(阻塞)狀態,並且釋放所持有的物件的鎖;
  • sleep():使一個正在執行的執行緒處於睡眠狀態,是一個靜態方法,呼叫此方法要處理InterruptedException異常;
  • notify():喚醒一個處於等待狀態的執行緒,當然在呼叫此方法的時候,並不能確切的喚醒某一個等待狀態的執行緒,而是由JVM確定喚醒哪個執行緒,而且與優先順序無關;
  • notityAll():喚醒所有處於等待狀態的執行緒,該方法並不是將物件的鎖給所有執行緒,而是讓它們競爭,只有獲得鎖的執行緒才能進入就緒狀態;

9、java如何實現多執行緒之間的通訊和協作?

  • Java提供了3個非常重要的方法來巧妙地解決執行緒間的通訊問題。這3個方法分別是:wait()、notify()和notifyAll()。
  • 它們都是Object類的最終方法,因此每一個類都預設擁有它們。雖然所有的類都預設擁有這3個方法,但是隻有在synchronized關鍵字作用的範圍內,並且是同一個同步問題中搭配使用這3個方法時才有實際的意義。這些方法在Object類中宣告的語法格式如下所示:
    • final void wait() throws InterruptedException
    • final void notify()
    • final void notifyAll()

10、為什麼執行緒通訊的方法wait(),notify()和notifyAll()被定義在Object類裡,為什麼不放在Thread類裡面?

  • 不把它放在Thread類裡的原因,一個很明顯的原因是JAVA提供的鎖是物件級的而不是執行緒級的,每個物件都有鎖,通過執行緒獲得,簡單的說,由於wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中因為鎖屬於物件
  • Java的每個物件中都有一個鎖(monitor,也可以成為監視器)並且wait(),notify()等方法用於等待物件的鎖或者通知其他執行緒物件的監視器可用
  • 在Java的執行緒中並沒有可供任何物件使用的鎖和同步器。這就是為什麼這些方法是Object類的一部分,這樣Java的每一個類都有用於執行緒間通訊的基本方法
  • Java API 的設計人員提供了一些方法當等待條件改變的時候通知它們,但是這些方法沒有完全實現。notify()方法不能喚醒某個具體的執行緒,所以只有一個執行緒在等待的時候它才有用武之地,而notifyAll()喚醒所有執行緒並允許他們爭奪鎖確保了至少有一個執行緒能繼續執行.

11、為什麼wait(),notify()和notifyAll()必須在同步方法或者同步塊中被呼叫?

  • 當一個執行緒需要呼叫物件的wait()方法的時候,這個執行緒必須擁有該物件的鎖,接著它就會釋放這個物件鎖並進入等待狀態直到其他執行緒呼叫這個物件上的notify()方法。同樣的,當一個執行緒需要呼叫物件的notify()方法時,它會釋放這個物件的鎖,以便其他在等待的執行緒就可以得到這個物件鎖。
  • 由於所有的這些方法都需要執行緒持有物件的鎖,這樣就只能通過同步來實現,所以他們只能在同步方法或者同步塊中被呼叫。
  • 主要是因為Java API強制要求這樣做,如果你不這麼做,你的程式碼會丟擲IllegalMonitorStateException異常。還有一個原因是為了避免wait和notify之間產生競態條件。

12、程序間的通訊方式

  • 管道( pipe):管道是一種半雙工的通訊方式,資料只能單向流動,而且只能在具有親緣關係的程序間使用。程序的親緣關係通常是指父子程序關係。
  • 有名管道 (named pipe) : 有名管道也是半雙工的通訊方式,但是它允許無親緣關係程序間的通訊。
  • 訊號量( semophore ) : 訊號量是一個計數器,可以用來控制多個程序對共享資源的訪問。它常作為一種鎖機制,防止某程序正在訪問共享資源時,其他程序也訪問該資源。因此,主要作為程序間以及同一程序內不同執行緒之間的同步手段。
  • 訊息佇列( message queue ) : 訊息佇列是由訊息的連結串列,存放在核心中並由訊息佇列識別符號標識。訊息佇列克服了訊號傳遞資訊少、管道只能承載無格式位元組流以及緩衝區大小受限等缺點。
  • 訊號 ( sinal ) : 訊號是一種比較複雜的通訊方式,用於通知接收程序某個事件已經發生。
  • 共享記憶體( shared memory ) :共享記憶體就是對映一段能被其他程序所訪問的記憶體,這段共享記憶體由一個程序建立,但多個程序都可以訪問。共享記憶體是最快的 IPC 方式,它是針對其他程序間通訊方式執行效率低而專門設計的。它往往與其他通訊機制,如訊號量,配合使用,來實現程序間的同步和通訊。
  • 套接字( socket ) : 套解口也是一種程序間通訊機制,與其他通訊機制不同的是,它可用於不同機器間的程序通訊。

13、sleep()和wait()有什麼區別?

  • sleep是執行緒類(Thread)的方法,導致此執行緒暫停執行指定時間,給執行機會給其他執行緒,但是監控狀態依然保持,到時後會自動恢復。呼叫sleep不會釋放物件鎖。
  • wait是Object類的方法,對此物件呼叫wait方法導致本執行緒放棄物件鎖,進入等待此物件的等待鎖定池,只有針對此物件發出notify方法(或notifyAll)後本執行緒才進入物件鎖定池準備獲得物件鎖進入執行狀態。

14、為什麼Thread類的sleep()和yield()方法是靜態的?

  • Thread類的sleep()和yield()方法將在當前正在執行的執行緒上執行。所以在其他處於等待狀態的執行緒上呼叫這些方法是沒有意義的。這就是為什麼這些方法是靜態的。
  • 它們可以在當前正在執行的執行緒中工作,並避免程式設計師錯誤的認為可以在其他非執行執行緒呼叫這些方法。

15、Java中CyclicBarrier 和 CountDownLatch有什麼不同?

  • CyclicBarrier 和 CountDownLatch 都可以用來讓一組執行緒等待其它執行緒。與 CyclicBarrier 不同的是,CountdownLatch 不能重新使用

16、為什麼需要並行設計?

  • 業務需求:業務上需要多個邏輯單元,比如多個客戶端要傳送請求
  • 效能需求:在多核OS中,使用多執行緒併發執行效能會比單執行緒執行的效能好很多

17、併發和並行的區別:

  • 舉例:
    • 你吃飯吃到一半,電話來了,你一直到吃完了以後才去接,這就說明你不支援併發也不支援並行。
    • 你吃飯吃到一半,電話來了,你停了下來接了電話,接完後繼續吃飯,這說明你支援併發。
    • 你吃飯吃到一半,電話來了,你一邊打電話一邊吃飯,這說明你支援並行。
  • 併發的關鍵是有處理多個任務的能力,但是不一定同時處理,而並行表示同一個時刻處理多個任務,兩者的關鍵點就是是否同時。
    • 解釋一:並行是指兩個或者多個執行緒在同一時刻發生;而併發是指兩個或多個執行緒在同一時間間隔發生(交替執行)
    • 解釋二:並行是在不同實體上的多個事件(多個JVM),併發是在同一實體上的多個事件(一個JVM)。
  • 並行又分在一臺處理器上同時處理多個任務,在多臺處理器上同時處理多個任務。如hadoop分散式叢集

18、什麼是Daemon(守護)執行緒?它有什麼意義?

  • 在Java中有兩類執行緒:使用者執行緒 (User Thread)、守護執行緒 (Daemon Thread)。
  • 所謂後臺(daemon)執行緒,是指在程式執行的時候在後臺提供一種通用服務的執行緒,並且這個執行緒並不屬於程式中不可或缺的部分。因此,當所有的非後臺執行緒介紹時,程式也就終止了,同時會殺死程序中的所有後臺執行緒。反過來說,只要有任何非後臺執行緒還在執行,程式就不會終止。必須線上程啟動之前呼叫setDaemon()方法,才能把它設定為後臺執行緒。注意:後臺程序在不執行finally子句的情況下就會終止其run()方法。
  • 守護執行緒和使用者執行緒的區別在於:守護執行緒依賴於建立它的執行緒,而使用者執行緒則不依賴。舉個簡單的例子:如果在main執行緒中建立了一個守護執行緒,當main方法執行完畢之後,守護執行緒也會隨著消亡。而使用者執行緒則不會,使用者執行緒會一直執行直到其執行完畢。在JVM中,像垃圾收集器執行緒就是守護執行緒。
  • 守護執行緒必須在使用者執行緒執行前呼叫,它是一個後臺服務執行緒,一個守護執行緒建立的子執行緒依然是守護執行緒。

19、如何建立守護執行緒?

使用Thread類的setDaemon(true)方法可以將執行緒設定為守護執行緒,需要注意的是,需要在呼叫start()方法前呼叫這個方法,否則會丟擲IllegalThreadStateException異常。

20、 如何停止一個執行緒

  • Java提供了很豐富的API但沒有為停止執行緒提供API。JDK1.0本來有一些像stop(), suspend() 和 resume()的控制方法但是由於潛在的死鎖威脅因此在後續的JDK版本中他們被棄用了,之後JavaAPI的設計者就沒有提供一個相容且執行緒安全的方法來停止一個執行緒。
  • 當run()或者call()方法執行完的時候執行緒會自動結束,如果要手動結束一個執行緒,可以用volatile布林變數來退出run()方法的迴圈或者是取消任務來中斷執行緒。
  • 當不阻塞時候設定一個標誌位,讓程式碼塊正常執行結束並停止執行緒。
  • 如果發生了阻塞,用interupt()方法,Thread.interrupt()方法不會中斷一個正在執行的執行緒。這一方法實際上完成的是,線上程受到阻塞時丟擲一箇中斷訊號,這樣執行緒就得以退出阻塞的狀態。

21、什麼是Thread Group?為什麼不建議使用它?

  • ThreadGroup是一個類,它的目的是提供關於執行緒組的資訊。
  • hreadGroup API比較薄弱,它並沒有比Thread提供了更多的功能。它有兩個主要的功能:一是獲取執行緒組中處於活躍狀態執行緒的列表;二是設定為執行緒設定未捕獲異常處理器(ncaughtexceptionhandler)。但在Java1.5中Thread類也添加了setUncaughtExceptionHandler(UncaughtExceptionHandlereh)方法,所以ThreadGroup是已經過時的,不建議繼續使用。

22、 什麼是Java執行緒轉儲(Thread Dump),如何得到它?

  • 執行緒轉儲是一個JVM活動執行緒的列表,它對於分析系統瓶頸和死鎖非常有用。>> - 有很多方法可以獲取執行緒轉儲——使用Profiler,Kill-3命令,jstack工具等等。我更喜歡jcmd命令(jdk1.8以上)。

23、什麼是FutureTask?

  • 在Java併發程式中FutureTask表示一個可以取消的非同步運算。它有啟動和取消運算、查詢運算是否完成和取回運算結果等方法。只有當運算完成的時候結果才能取回,如果運算尚未完成get方法將會阻塞。一個FutureTask物件可以對呼叫了Callable和Runnable的物件進行包裝,由於FutureTask也是呼叫了Runnable介面所以它可以提交給Executor來執行。

24、Java中interrupted 和 isInterruptedd方法的區別?

  • interrupted() :會將中斷狀態清除,Java多執行緒的中斷機制是用內部標識來實現的,呼叫Thread.interrupt()來中斷一個執行緒就會設定中斷標識為true。當中斷執行緒呼叫靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。
  • isInterruptedd : 不會將中斷狀態清除,非靜態方法isInterrupted()用來查詢其它執行緒的中斷狀態且不會改變中斷狀態標識。
  • 簡單的說就是任何丟擲InterruptedException異常的方法都會將中斷狀態清零。無論如何,一個執行緒的中斷狀態有有可能被其它執行緒呼叫中斷來改變。

25、為什麼你應該在迴圈中檢查等待條件?

  • 處於等待狀態的執行緒可能會收到錯誤警報和偽喚醒,如果不在迴圈中檢查等待條件,程式就會在沒有滿足結束條件的情況下退出。因此,當一個等待執行緒醒來時,不能認為它原來的等待狀態仍然是有效的,在notify()方法呼叫之後和等待執行緒醒來之前這段時間它可能會改變。這就是在迴圈中使用wait()方法效果更好的原因。

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

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

27、java 的記憶體模型是什麼?《Java併發程式設計實踐》16章

  • Java記憶體模型規定和指引Java程式在不同的記憶體架構、CPU和作業系統間有確定性地行為。它在多執行緒的情況下尤其重要。Java記憶體模型對一個執行緒所做的變動能被其它執行緒可見提供了保證,它們之間是先行發生關係。這個關係定義了一些規則讓程式設計師在併發程式設計時思路更清晰
  • 執行緒內的程式碼能夠按先後順序執行,這被稱為程式次序規則。 對於同一個鎖,一個解鎖操作一定要發生在時間上後發生的另一個鎖定操作之前,也叫做管程鎖定規則。
  • 前一個對volatile的寫操作在後一個volatile的讀操作之前,也叫volatile變數規則。
  • 一個執行緒內的任何操作必需在這個執行緒的start()呼叫之後,也叫作執行緒啟動規則。
  • 一個執行緒的所有操作都會線上程終止之前,執行緒終止規則。
  • 一個物件的終結操作必需在這個物件構造完成之後,也叫物件終結規則。

28、Java中堆和棧有什麼不同?

  • 棧是一塊和執行緒緊密相關的記憶體區域,每個執行緒都有自己的棧記憶體,用於儲存本地變數,方法引數和棧呼叫,一個執行緒中儲存的變數對其它執行緒是不可見的。
  • 堆是所有執行緒共享的一片公用記憶體區域,物件都在堆裡建立,為了提升效率執行緒會從堆中弄一個快取到自己的棧,如果多個執行緒使用該變數就可能引發問題,這時volatile 變數就可以發揮作用了,它要求執行緒從主存中讀取變數的值。

29、 什麼是執行緒池? 為什麼要使用它?

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

30、CachedThreadPool 、 FixedThreadPool、SingleThreadPool

  • newSingleThreadExecutor:
    • 建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務, 保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行
    • 適用場景:任務少 ,並且不需要併發執行
  • newCachedThreadPool :
    • 建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒.執行緒沒有任務要執行時,便處於空閒狀態,處於空閒狀態的執行緒並不會被立即銷燬(會被快取住),只有當空閒時間超出一段時間(預設為60s)後,執行緒池才會銷燬該執行緒(相當於清除過時的快取)。新任務到達後,執行緒池首先會讓被快取住的執行緒(空閒狀態)去執行任務,如果沒有可用執行緒(無空閒執行緒),便會建立新的執行緒。
    • 適用場景:處理任務速度>提交任務速度,耗時少的任務(避免無限新增執行緒)
  • newFixedThreadPool:
    • 建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
  • newScheduledThreadPool:建立一個定長執行緒池,支援定時及週期性任務執行

31、ThreadPoolExecutor

  • 構造方法引數說明
    • corePoolSize:核心執行緒數,預設情況下核心執行緒會一直存活,即使處於閒置狀態也不會受存keepAliveTime限制。除非將allowCoreThreadTimeOut設定為true。
    • maximumPoolSize:執行緒池所能容納的最大執行緒數。超過這個數的執行緒將被阻塞。當任務佇列為沒有設定大小的LinkedBlockingDeque時,這個值無效。
    • keepAliveTime:非核心執行緒的閒置超時時間,超過這個時間就會被回收。 unit:指定keepAliveTime的單位,如TimeUnit.SECONDS。當將allowCoreThreadTimeOut設定為true時對corePoolSize生效。
    • workQueue:執行緒池中的任務佇列.常用的有三種佇列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。
    • threadFactory:執行緒工廠,提供建立新執行緒的功能。ThreadFactory是一個介面,只有一個方法
  • 原理
    • 如果當前池大小 poolSize 小於 corePoolSize,則建立新執行緒執行任務。
    • 如果當前池大小poolSize大於corePoolSize,且等待佇列未滿,則進入等待佇列
    • 如果當前池大小 poolSize 大於 corePoolSize 且小於 maximumPoolSize ,且等待佇列已滿,則建立新執行緒執行任務。
    • 如果當前池大小 poolSize 大於 corePoolSize 且大於 maximumPoolSize ,且等待佇列已滿,則呼叫拒絕策略來處理該任務。
  • 執行緒池裡的每個執行緒執行完任務後不會立刻退出,而是會去檢查下等待佇列裡是否還有執行緒任務需要執行,如果在 keepAliveTime 裡等不到新的任務了,那麼執行緒就會退出。

32、CopyOnWriteArrayList

  • CopyOnWriteArrayList : 寫時加鎖,當新增一個元素的時候,將原來的容器進行copy,複製出一個新的容器,然後在新的容器裡面寫,寫完之後再將原容器的引用指向新的容器,而讀的時候是讀舊容器的資料,所以可以進行併發的讀,但這是一種弱一致性的策略。
  • 使用場景:CopyOnWriteArrayList適合使用在讀操作遠遠大於寫操作的場景裡,比如快取。

33、Executor拒絕策略

  • Executor框架同java.util.concurrent.Executor 介面在Java 5中被引入。Executor框架是一個根據一組執行策略呼叫,排程,執行和控制的非同步任務的框架,利用Executors框架可以非常方便的建立一個執行緒池.
  • AbortPolicy: 為java執行緒池預設的阻塞策略,不執行此任務,而且直接丟擲一個執行時異常,切記ThreadPoolExecutor.execute需要try catch,否則程式會直接退出.
  • DiscardPolicy: 直接拋棄,任務不執行,空方法
  • DiscardOldestPolicy:從佇列裡面拋棄head的一個任務,並再次execute 此task。
  • CallerRunsPolicy:在呼叫execute的執行緒裡面執行此command,會阻塞入
  • 使用者自定義拒絕策略:實現RejectedExecutionHandler,並自己定義策略模式

34、如果你提交任務時,執行緒池佇列已滿。會時發會生什麼?

  • 事實上如果一個任務不能被排程執行那麼ThreadPoolExecutor’s submit()方法將會丟擲一個RejectedExecutionException異常。

35、Java執行緒池中submit() 和 execute()方法有什麼區別?

  • 兩個方法都可以向執行緒池提交任務,execute()方法的返回型別是void,它定義在Executor介面中, 而submit()方法可以返回持有計算結果的Future物件,它定義在ExecutorService介面中,它擴充套件了Executor介面,其它執行緒池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法

36、Swing是執行緒安全的嗎? 為什麼?

  • 你可以很肯定的給出回答,Swing不是執行緒安全的,但是你應該解釋這麼回答的原因即便面試官沒有問你為什麼。當我們說swing不是執行緒安全的常常提到它的元件,這些元件不能在多執行緒中進行修改,所有對GUI元件的更新都要在AWT執行緒中完成,而Swing提供了同步和非同步兩種回撥方法來進行更新

37、Swing API中那些方法是執行緒安全的?

  • 這個問題又提到了swing和執行緒安全,雖然元件不是執行緒安全的但是有一些方法是可以被多執行緒安全呼叫的,比如repaint(), revalidate()。 JTextComponent的setText()方法和JTextArea的insert() 和 append() 方法也是執行緒安全的。

38、多執行緒中的忙迴圈是什麼?

  • 忙迴圈就是程式設計師用迴圈讓一個執行緒等待,不像傳統方法wait(), sleep() 或 yield() 它們都放棄了CPU控制,而忙迴圈不會放棄CPU,它就是在執行一個空迴圈。這麼做的目的是為了保留CPU快取,在多核系統中,一個等待執行緒醒來的時候可能會在另一個核心執行,這樣會重建快取。為了避免重建快取和減少等待重建的時間就可以使用它了

39、如果同步塊內的執行緒丟擲異常會發生什麼?

  • 這個問題坑了很多Java程式設計師,若你能想到鎖是否釋放這條線索來回答還有點希望答對。無論你的同步塊是正常還是異常退出的,裡面的執行緒都會釋放鎖,所以對比鎖介面我更喜歡同步塊,因為它不用我花費精力去釋放鎖,該功能可以在finally block裡釋放鎖實現。

40、單例模式的雙檢鎖是什麼?

  • 這個問題在Java面試中經常被問到,但是面試官對回答此問題的滿意度僅為50%。一半的人寫不出雙檢鎖還有一半的人說不出它的隱患和Java1.5是如何對它修正的。它其實是一個用來建立執行緒安全的單例的老方法,當單例例項第一次被建立時它試圖用單個鎖進行效能優化,但是由於太過於複雜在JDK1.4中它是失敗的,我個人也不喜歡它。無論如何,即便你也不喜歡它但是還是要了解一下,因為它經常被問到。

41、寫出3條你遵循的多執行緒最佳實踐

  • 給你的執行緒起個有意義的名字。
    • 這樣可以方便找bug或追蹤。OrderProcessor, QuoteProcessor or TradeProcessor 這種名字比 Thread-1. Thread-2 and Thread-3 好多了,給執行緒起一個和它要完成的任務相關的名字,所有的主要框架甚至JDK都遵循這個最佳實踐。
  • 避免鎖定和縮小同步的範圍
    • 鎖花費的代價高昂且上下文切換更耗費時間空間,試試最低限度的使用同步和鎖,縮小臨界區。因此相對於同步方法我更喜歡同步塊,它給我擁有對鎖的絕對控制權。
  • 多用同步類少用wait 和 notify
    • 首先,CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 這些同步類簡化了編碼操作,而用wait和notify很難實現對複雜控制流的控制。其次,這些類是由最好的企業編寫和維護在後續的JDK中它們還會不斷優化和完善,使用這些更高等級的同步工具你的程式可以不費吹灰之力獲得優化。
  • 多用併發集合少用同步集合
    • 這是另外一個容易遵循且受益巨大的最佳實踐,併發集合比同步集合的可擴充套件性更好,所以在併發程式設計時使用併發集合效果更好。如果下一次你需要用到map,你應該首先想到用ConcurrentHashMap

42、如何強制啟動一個執行緒?

  • 這個問題就像是如何強制進行Java垃圾回收,目前還沒有覺得方法,雖然你可以使用System.gc()來進行垃圾回收,但是不保證能成功。在Java裡面沒有辦法強制啟動一個執行緒,它是被執行緒排程器控制著且Java沒有公佈相關的API。

43、Java中invokeAndWait 和 invokeLater有什麼區別?

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

42、如何合理的配置java執行緒池?

  • 如CPU密集型的任務,基本執行緒池應該配置多大?IO密集型的任務,基本執行緒池應該配置多大?用有界佇列好還是無界佇列好?任務非常多的時候,使用什麼阻塞佇列能獲取最好的吞吐量?
    • 配置執行緒池時CPU密集型任務可以少配置執行緒數,大概和機器的cpu核數相當,可以使得每個執行緒都在執行任務
    • IO密集型時,大部分執行緒都阻塞,故需要多配置執行緒數,2*cpu核數
    • 有界佇列和無界佇列的配置需區分業務場景,一般情況下配置有界佇列,在一些可能會有爆發性增長的情況下使用無界佇列。
    • 任務非常多時,使用非阻塞佇列使用CAS操作替代鎖可以獲得好的吞吐量。

43、如何寫程式碼來解決生產者消費者問題?

  • 在現實中你解決的許多執行緒問題都屬於生產者消費者模型,就是一個執行緒生產任務供其它執行緒進行消費,你必須知道怎麼進行執行緒間通訊來解決這個問題。比較低階的辦法是用wait和notify來解決這個問題,比較讚的辦法是用Semaphore 或者 BlockingQueue來實現生產者消費者模型
  • 實現生產者消費者模型是多執行緒程式經典問題之一,它描述是有一塊緩衝區作為倉庫,生產者可以將產品放入倉庫,消費者則可以從倉庫中取走產品。兩個解決方法:
    • (1)採用某種機制保護生產者和消費者之間的同步,較高的效率並且易於實現,程式碼的可控制性較好,常用。
    • (2)在生產者和消費者之間建立一個管道,管道緩衝區不易控制,被傳輸資料物件不易於封裝等,實用性不強。
  • 同步問題核心在於:如何保證同一資源被多個執行緒併發訪問時的完整性。常用的同步方法是採用訊號或加鎖機制,保證資源在任意時刻至多被一個執行緒訪問.三個同步方法,一個管道方法。
    • wait() / notify()方法
    • await() / signal()方法
    • BlockingQueue阻塞佇列方法//JDK5.0的新增內容
    • 實現方式採用的是我們第2種await() / signal()方法,它可以在生成物件時指定容量大小。它用於阻塞操作的是put()和take()方法。
    • put()方法:類似於我們上面的生產者執行緒,容量達到最大時,自動阻塞。
    • take()方法:類似於我們上面的消費者執行緒,容量為0時,自動阻塞。
    • Semaphore方法
    • 訊號量(Semaphore)維護了一個許可集。在許可可用前會阻塞每一個 acquire(),然後再獲取該許可,是用來控制同時訪問特定資源的執行緒數量,它通過協調各個執行緒,以保證合理的使用公共資源 Semaphore可以用於做流量控制,特別公用資源有限的應用場景,比如資料庫連線。假如有一個需求,要讀取幾萬個檔案的資料,因為都是IO密集型任務,我們可以啟動幾十個執行緒併發的讀取,但是如果讀到記憶體後,還需要儲存到資料庫中,而資料庫的連線數只有10個,這時我們必須控制只有十個執行緒同時獲取資料庫連線儲存資料,否則會報錯無法獲取資料庫連線。
    • 每個 release() 新增一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可物件,Semaphore 只對可用許可的號碼進行計數,並採取相應的行動。
    • Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的執行緒數目。
    • 注意,呼叫acquire()時無法保持同步鎖,因為這會阻止將項返回到池中。訊號量封裝所需的同步,以限制對池的訪問,這同維持該池本身一致性所需的同步是分開的。同步令牌(notFull.acquire())必須在互斥令牌(mutex.acquir())前面獲得,如果先得到互斥鎖再發生等待,會造成死鎖。
    • PipedInputStream / PipedOutputStream方法

44、什麼是Callable和Future?

  • Java 5在concurrency包中引入了java.util.concurrent.Callable 介面,它和Runnable介面很相似,但它可以返回一個物件或者丟擲一個異常。
  • Callable介面使用泛型去定義它的返回型別。Executors類提供了一些有用的方法去線上程池中執行Callable內的任務。由於Callable任務是並行的,我們必須等待它返回的結果。
  • java.util.concurrent.Future物件為我們解決了這個問題。線上程池提交Callable任務後返回了一個Future物件,使用它我們可以知道Callable任務的狀態和得到Callable返回的執行結果。
  • Future提供了get()方法讓我們可以等待Callable結束並獲取它的執行結果。
  • FutureTask是Future的一個基礎實現,我們可以將它同Executors使用處理非同步任務。通常我們不需要使用FutureTask類,單當我們打算重寫Future介面的一些方法並保持原來基礎的實現是,它就變得非常有用。我們可以僅僅繼承於它並重寫我們需要的方法。閱讀Java FutureTask例子,學習如何使用它。

45、多讀少寫的場景應該使用哪個併發容器,為什麼使用它?

  • 比如你做了一個搜尋引擎,搜尋引擎每次搜尋前需要判斷搜尋關鍵詞是否在黑名單裡,黑名單每天更新一次。
    • CopyOnWriteArrayList這個容器適用於多讀少寫…
    • 讀寫並不是在同一個物件上。在寫時會大面積複製陣列,所以寫的效能差,在寫完成後將讀的引用改為執行寫的物件

46、Java裡的阻塞佇列

  • 7個佇列阻塞
    • ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列。
    • LinkedBlockingQueue :一個由連結串列結構組成的有界阻塞佇列。
    • PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。
    • DelayQueue:一個使用優先順序佇列實現的無界阻塞佇列。
    • SynchronousQueue:一個不儲存元素的阻塞佇列。
    • LinkedTransferQueue:一個由連結串列結構組成的無界阻塞佇列。
    • LinkedBlockingDeque:一個由連結串列結構組成的雙向阻塞佇列。
  • 新增元素
    • Java中的阻塞佇列介面BlockingQueue繼承自Queue介面。BlockingQueue介面提供了3個新增元素方法。
    • add:新增元素到佇列裡,新增成功返回true,由於容量滿了新增失敗會丟擲IllegalStateException異常
    • offer:新增元素到佇列裡,新增成功返回true,新增失敗返回false
    • put:新增元素到佇列裡,如果容量滿了會阻塞直到容量不滿
  • 刪除方法
    • poll:刪除佇列頭部元素,如果佇列為空,返回null。否則返回元素。
    • remove:基於物件找到對應的元素,並刪除。刪除成功返回true,否則返回false
    • take:刪除佇列頭部元素,如果佇列為空,一直阻塞到佇列有元素並刪除

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

  • java.util.Timer是一個工具類,可以用於安排一個執行緒在未來的某個特定時間執行。Timer類可以用安排一次性任務或者週期任務。
  • java.util.TimerTask是一個實現了Runnable介面的抽象類,我們需要去繼承這個類來建立我們自己的定時任務並使用Timer去安排它的執行。

48、什麼是原子操作?在Java Concurrency API中有哪些原子類(atomic classes)?

  • java.util.concurrent.atomic包下,可以分為四種類型的原子更新類:原子更新基本型別、原子更新陣列型別、原子更新引用和原子更新屬性。
  • 原子更新基本型別
    • AtomicBoolean:原子更新布林變數
    • AtomicInteger:原子更新整型變數
    • AtomicLong:原子更新長整型變數
  • 原子更新陣列
    • AtomicIntegerArray:原子更新整型陣列的某個元素
    • AtomicLongArray:原子更新長整型陣列的某個元素
    • AtomicReferenceArray:原子更新引用型別陣列的某個元素
    • AtomicIntegerArray常用的方法有:
    • int addAndSet(int i, int delta):以原子方式將輸入值與陣列中索引為i的元素相加 boolean compareAndSet(int i,
    • int expect, int update):如果當前值等於預期值,則以原子方式更新陣列中索引為i的值為update值
  • 原子更新引用型別
    • AtomicReference:原子更新引用型別
    • AtomicReferenceFieldUpdater:原子更新引用型別裡的欄位
    • AtomicMarkableReference:原子更新帶有標記位的引用型別。
  • 原子更新欄位類
    • AtomicIntegerFieldUpdater:原子更新整型欄位
    • AtomicLongFieldUpdater:原子更新長整型欄位
    • AtomicStampedReference:原子更新帶有版本號的引用型別。
  • 更新欄位,需要兩個步驟:
    • 每次必須使用newUpdater建立一個更新器,並且需要設定想要更新的類的欄位
    • 更新類的欄位(屬性)必須為public volatile
    • 原子操作是指一個不受其他操作影響的操作任務單元。原子操作是在多執行緒環境下避免資料不一致必須的手段。
    • int++並不是一個原子操作,所以當一個執行緒讀取它的值並加1時,另外一個執行緒有可能會讀到之前的值,這就會引發錯誤。
    • 為了解決這個問題,必須保證增加操作是原子的,在JDK1.5之前我們可以使用同步技術來做到這一點。到JDK1.5,java.util.concurrent.atomic包提供了int和long型別的裝類,它們可以自動的保證對於他們的操作是原子的並且不需要使用同步。

49、有三個執行緒T1,T2,T3,怎麼確保它們按順序執行?

  • 在多執行緒中有多種方法讓執行緒按特定順序執行,你可以用執行緒類的join()方法在一個執行緒中啟動另一個執行緒,另外一個執行緒完成該執行緒繼續執行。為了確保三個執行緒的順序你應該先啟動最後一個(T3呼叫T2,T2呼叫T1),這樣T1就會先完成而T3最後完成。你可以檢視這篇文章瞭解更多。

50、執行緒作⽤

  • 發揮多核CPU的優勢 如果是單執行緒的程式,那麼在雙核CPU上就浪費了50%,在4核CPU上就浪費了75%。多線可以充分利⽤CPU的。 防⽌阻塞 多條執行緒同時運⾏,⼀條執行緒的程式碼執⾏阻塞,也不會影響其它任務的執⾏。

51、Thread類中的yield方法有什麼作用?

  • Yield方法可以暫停當前正在執行的執行緒物件,讓其它有相同優先順序的執行緒執行。它是一個靜態方法而且只保證當前執行緒放棄CPU佔用而不能保證使其它執行緒一定能佔用CPU,執行yield()的執行緒有可能在進入到暫停狀態後馬上又被執行。點選這裡檢視更多yield方法的相關內容。

52、Runnable接⼝和Callable接⼝的區別

  • Runnable接⼝中的run()⽅法的返回值是void,它只是純粹地去執⾏run()⽅法中的程式碼⽽已;
  • Callable接⼝中的call()⽅法是有返回值的,是⼀個泛型,和Future、FutureTask配合可以⽤來獲 取非同步執⾏的結果。

53、執行緒安全的級別

程式碼在多執行緒下執⾏和在單執行緒下執⾏永遠都能獲得⼀樣的結果,那麼程式碼就是執行緒安全的。執行緒安全也是有級別之分的:

  • 不可變: 像String、Integer、Long這些,都是final型別的類,要改變除⾮新建立⼀個。
  • 絕對執行緒安全: 不管運⾏時環境如何都不需要額外的同步措施。Java中有絕對執行緒安全的類,⽐如CopyOnWriteArrayList、CopyOnWriteArraySet。
  • 相對執行緒安全: 像Vector這種,add、remove⽅法都是原⼦操作,不會被打斷。如果有個執行緒在遍歷某個Vector,同時另⼀個執行緒對其結構進⾏修改,會出現ConcurrentModificationException(failfast機制)。
  • 執行緒⾮安全: 這個就沒什麼好說的了,ArrayList、LinkedList、HashMap等都是執行緒⾮安全的類。

54、java中的鎖

  • 在Java多執行緒中,synchronized實現執行緒之間同步互斥,JDK1.5以後,Java類庫中新增了Lock介面用來實現鎖功能。
  • 鎖為對共享資料進行保護,同一把鎖保護的共享資料,任何執行緒訪問都需要先持有該鎖。一把鎖一個執行緒,當該鎖的持有執行緒對資料訪問結束之後必須釋放該鎖,讓其他執行緒持有。鎖的持有執行緒在鎖的獲得和鎖的釋放之間的這段時間所執行的程式碼被稱為臨界區。
  • 鎖能夠保護共享資料以實現執行緒安全,主要作用有保障原子性、保障可見性和保障有序性。由於鎖具有互斥性,因此當執行緒執行臨界區中的程式碼時,其他執行緒無法做到干擾,臨界區中的程式碼也就具有了不可分割的原子特性。
  • 鎖具有排他性,即一個鎖一次只能被一個執行緒持有,被稱之為排他鎖或互斥鎖。當然,新版JDK為了效能優化,推出了讀寫鎖,讀寫鎖是排它鎖的改進。 5.按照Java虛擬機器對鎖的實現方式劃分,Java平臺中的鎖包括內部鎖(主要是通過synchronized實現)和顯式鎖(主要是通過Lock介面及其實現類實現).

55、公平鎖和非公平鎖:

  • 鎖Lock分為”公平鎖”和”非公平鎖”:
    • 公平鎖表示執行緒獲取鎖的順序是按照執行緒加鎖的順序來分配的,即先來先得的FIFO先進先出順序。
    • 非公平鎖就是一種獲取鎖的搶佔機制,是隨機獲得鎖的,先來的不一定先得到鎖,可能造成某些執行緒一直拿不到鎖,即不公平了。

56、內部鎖——眾所周知的synchronized

  • Java平臺中的任何一個物件都有唯一一個與之關聯的鎖,這種鎖被稱之為監視器(或者叫內部鎖)。內部鎖是一種排它鎖,它能保證原子性、可見性和有序性。內部鎖就由synchronized關鍵字實現。
  • synchronized可以修飾方法或者程式碼塊.
    • synchronized修飾方法,該方法內部的程式碼就屬於一個臨界區,該方法就屬於一個同步方法。此時一個執行緒對該方法內部的變數的更新就保證了原子性和可見性,從而實現了執行緒安全.
    • synchronized修飾程式碼塊,需要一個鎖控制代碼(一個物件的引用或者是一個可以返回物件的表示式),此時synchronized關鍵字引導的程式碼塊就是臨界區;同步塊的鎖控制代碼可以寫為this關鍵字,表示當前物件,鎖控制代碼對應的監視器就被稱之為相應同步塊的引導鎖。
    • 作為鎖控制代碼的變數通常以private final修飾,防止鎖控制代碼變數的值改變之後,導致執行同一個同步塊的多個執行緒使用不同的鎖,從而避免了競態。
  • 注意Java虛擬機器會為每一個內部鎖分配一個入口集用於存放等待獲得相應內部鎖的執行緒,當內部鎖的持有執行緒釋放當前鎖的時候,可能是入口集中處於BLOCKED狀態的執行緒獲得當前鎖也可能是處於RUNNABLE狀態的其他執行緒。內部鎖的競爭是激烈的,也是不公平的,可能等待了長時間的執行緒沒有獲得鎖,也可能是沒有經過等待的執行緒直接就獲得了鎖。

57、顯式的加鎖和解鎖——Lock介面

  • 在Java5.0之前,在協調對共享物件的訪問時可以使用的機制只有synchronized和volatile,在Java 5.0中:Lock介面(以及其實現類如ReentrantLock等),Lock介面中定義了一組抽象的加鎖操作。不同的是,synchronized可以方便的隱式的獲取鎖,而Lock介面則提供了一種顯式獲取鎖(排它鎖)。

58、重入鎖——ReentrantLock類

  • 如果一個執行緒持有一個鎖的時候還能繼續成功的申請該鎖,那麼我們就稱該鎖是可重入的,否則我們就稱該鎖是非可重入的。
    • ReentrantLock是一個可重入鎖,ReentrantLock類與synchronized類似,都可以實現執行緒之間的同步互斥。但ReentrantLock類此外還擴充套件了更多的功能,如嗅探鎖定、多路分支通知等,在使用上也比synrhronized更加的靈活。
    • 執行緒A和B都要獲取物件O的鎖定,假設A獲取了物件O鎖,B將等待A釋放對O的鎖定, 如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷 如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以後,中斷等待,而幹別的事情
    • ReentrantLock是一個既公平又非公平的顯示鎖,所以在例項化ReentrantLock類時,ReentrantLock的一個構造簽名為ReentrantLock(boolean fair),傳入true時是公平鎖。公平鎖的開銷較非公平鎖的開銷大,因此顯式鎖預設使用的是非公平的排程策略。
    • 預設情況下使用內部鎖,而當多數執行緒持有一個鎖的時間相對較長或者執行緒申請鎖的平均時間間隔相對長的情況下我們可以考慮使用顯式鎖。
  • ReentrantLock獲取鎖定與三種方式
    • lock(), 如果獲取了鎖立即返回,如果別的執行緒持有鎖,當前執行緒則一直處於休眠狀態,直到獲取鎖
    • tryLock(), 如果獲取了鎖立即返回true,如果別的執行緒正持有鎖,立即返回false; c)tryLock(long timeout,TimeUnit unit),如果獲取了鎖定立即返回true,如果別的執行緒正持有鎖,會等待引數給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false;
    • lockInterruptibly:如果獲取了鎖定立即返回,如果沒有獲取鎖定,當前執行緒處於休眠狀態,直到或者鎖定,或者當前執行緒被別的執行緒中斷

59、synchronized和ReentrantLock對比

  • 在資源競爭不是很激烈的情況下,偶爾會有同步的情形下,synchronized是很合適的。原因在於,編譯程式通常會盡可能的進行優化synchronized,另外可讀性非常好,不管用沒用過5.0多執行緒包的程式設計師都能理解。
  • ReentrantLock提供了多樣化的同步,比如有時間限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在資源競爭不激烈的情形下,效能稍微比synchronized差點點。但是當同步非常激烈的時候,synchronized的效能一下子能下降好幾十倍。而ReentrantLock確還能維持常態。

60、讀寫鎖——(Read/WriteLock):主要用於讀執行緒持有鎖的時間比較長的情景下。

  • ReadWriteLock介面是對讀寫鎖的抽象,其預設的實現類是ReentrantReadWriteLock。ReadWriteLock定義了兩個方法readLock()和writeLock(),分別用於返回相應讀寫鎖例項的讀鎖和寫鎖。這兩個方法的返回值型別都是Lock。
  • 讀寫鎖是一種改進型的排它鎖,讀寫鎖允許多個執行緒可以同時讀取(只讀)共享變數
    • 讀寫鎖是分為讀鎖和寫鎖兩種角色的,讀執行緒在訪問共享變數的時候必須持有相應讀寫鎖的讀鎖,而且讀鎖是共享的、多個執行緒可以共同持有的;
    • 寫鎖是排他的,以一個執行緒在持有寫鎖的時候,其他執行緒無法獲得相應鎖的寫鎖或讀鎖。總之,讀寫鎖通過讀寫鎖的分離從而提高了併發性。

61、鎖的替代

  • 多個執行緒共享同一個非執行緒安全物件時,我們往往採用鎖來保證執行緒安全性,但是,鎖也有其弊端,比如鎖的開銷和在使用鎖的時候容易發生死鎖等
  • Java中也提供了一些對於某些情況下替代鎖的同步機制解決方案,如volatile關鍵字、final關鍵字、static關鍵字、原子變數以及各種併發容器和框架.
  • 策略模式:
    • 採用執行緒特有物件: 各個不同的執行緒建立各自的例項,一個例項只能被一個執行緒訪問的物件就被稱之為執行緒的特有物件。採用執行緒特有物件,保障了對非執行緒安全物件的訪問的執行緒安全。 只讀共享:在沒有額外同步的情況下,共享的只讀物件可以有可以由多個執行緒併發訪問,但是任何執行緒都不能修改它。共享的只讀物件包括不可變物件和事實不可變物件。
    • 執行緒安全共享:執行緒安全的物件在其內部實現同步,多個執行緒可以通過物件的公有介面來進行訪問而不需要進一步的同步。 保護物件:被保護的物件只能通過持有特定的鎖來訪問。保護物件包括封裝在其他執行緒安全物件中的物件,以及已釋出的並且由某個特定鎖保護的物件。 > - volatile關鍵字、ThreadLocal二者在鎖的某些功能上的替代作用:如下

62、什麼是ThreadLocal?

  • ThreadLocal是Java裡一種特殊的變數。它是為建立代價高昂的物件獲取執行緒安全的好方法,比如你可以用ThreadLocal讓SimpleDateFormat變成執行緒安全的,因為那個類建立代價高昂且每次呼叫都需要建立不同的例項所以不值得在區域性範圍使用它,如果為每個執行緒提供一個自己獨有的變數拷貝,將大大提高效率。首先,通過複用減少了代價高昂的物件的建立個數。其次,你在沒有使用高代價的同步或者不變性的情況下獲得了執行緒安全。執行緒區域性變數的另一個不錯的例子是ThreadLocalRandom類,它在多執行緒環境中減少了建立代價高昂的Random物件的個數
  • ThreadLocal用於建立執行緒的本地變數,我們知道一個物件的所有執行緒會共享它的全域性變數,所以這些變數不是執行緒安全的,我們可以使用同步技術。但是當我們不想使用同步的時候,我們可以選擇ThreadLocal變數。
  • hreadLocal為每個執行緒維護一個本地變數:採用空間換時間,它用於執行緒間的資料隔離,為每一個使用該變數的執行緒提供一個副本,每個執行緒都可以獨立地改變自己的副本,而不會和其他執行緒的副本衝突。
  • ThreadLocal類中維護一個Map,用於儲存每一個執行緒的變數副本,Map中元素的鍵為執行緒物件,而值為對應執行緒的變數副本。

63、Java中的volatile 變數是什麼?

  • volatile是一個特殊的修飾符,只有成員變數才能使用它,是java提供的一種同步手段,只不過它是輕量級的同步。在Java併發程式缺少同步類的情況下,多執行緒對成員變數的操作對其它執行緒是透明的。volatile變數可以保證下一個讀取操作會在前一個寫操作之後發生,就是上一題的volatile變數規則。
  • 所以執行緒都會直接讀取該變數並且不快取它。這就確保了執行緒讀取到的變數是同記憶體中是一致的。
  • 任何被volatile修飾的變數,都不拷貝副本到工作記憶體,任何 修改都及時寫在主存
  • 要使 volatile 變數提供理想的執行緒安全,必須同時滿足下面兩個條件: 對變數的寫操作不依賴於當前值。 該變數沒有包含在具有其他變數的不變式中.

64、什麼場景下可以使用volatile替換synchronized?

  • 只需要保證共享資源的可見性的時候可以使用volatile替代,synchronized保證可操作的原子性一致性和可見性。volatile適用於新值不依賴於就值的情形。
  • Volatile和Synchronized四個不同點:
    • 粒度不同,前者針對變數 ,後者鎖物件和類
    • syn阻塞,volatile執行緒不阻塞
    • syn保證三大特性,volatile不保證原子性
    • syn編譯器優化,volatile不優化

65、樂觀鎖與悲觀鎖(併發程式設計)

  • 悲觀鎖:總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。再比如Java裡面的同步原語synchronized關鍵字的實現也是悲觀鎖。
    • 在多執行緒競爭下,加鎖、釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題。
    • 一個執行緒持有鎖會導致其它所有需要此鎖的執行緒掛起。
    • 如果一個優先順序高的執行緒等待一個優先順序低的執行緒釋放鎖會導致優先順序倒置,引起效能風險。
  • 樂觀鎖:
    • 顧名思義,就是很樂觀,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,可以使用版本號等機制。樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量,像資料庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變數類就是使用了樂觀鎖的一種實現方式CAS實現的。
    • CAS是樂觀鎖技術:當多個執行緒嘗試使用CAS同時更新同一個變數時,只有其中一個執行緒能更新變數的值,而其它執行緒都失敗,失敗的執行緒並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。
    • CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。

66、當一個執行緒進入某個物件的一個synchronized的例項方法後,其它執行緒是否可進入此物件的其它方法?

  • A、一個執行緒在訪問一個物件的同步方法時,另一個執行緒可以同時訪問這個物件的非同步方法
  • B、 一個執行緒在訪問一個物件的同步方法時,另一個執行緒不能同時訪問這個同步方法。

67、synchronized和java.util.concurrent.locks.Lock的異同?

  • Lock 和 synchronized 有一點明顯的區別 —— lock 必須在 finally 塊中釋放。否則,如果受保護的程式碼將丟擲異常,鎖就有可能永遠得不到釋放!這一點區別看起來可能沒什麼,但是實際上,它極為重要。忘記在 finally 塊中釋放鎖,可能會在程式中留下一個定時炸彈,當有一天炸彈爆炸時,您要花費很大力氣才有找到源頭在哪。而使用同步,JVM 將確保鎖會獲得自動釋放。

68、SynchronizedMap和ConcurrentHashMap有什麼區別?

  • java5中新增了ConcurrentMap介面和它的一個實現類ConcurrentHashMap。ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的鎖機制,比起synchronizedMap來,它提供了好得多的併發性
    • 多個讀操作幾乎總可以併發地執行,同時進行的讀和寫操作通常也能併發地執行,而同時進行的寫操作仍然可以不時地併發進行(相關的類也提供了類似的多個讀執行緒的併發性,但是,只允許有一個活動的寫執行緒)
    • Hashtable中採用的鎖機制是一次鎖住整個hash表,從而同一時刻只能由一個執行緒對其進行操作;而ConcurrentHashMap中則是一次鎖住一個桶。ConcurrentHashMap預設將hash表分為16個桶,諸如get,put,remove等常用操作只鎖當前需要用到的桶。這樣,原來只能一個執行緒進入,現在卻能同時有16個寫執行緒執行,併發效能的提升是顯而易見的。前面說到的16個執行緒指的是寫執行緒,而讀操作大部分時候都不需要用到鎖。只有在size等操作時才需要鎖住整個hash表。
    • 在迭代方面,ConcurrentHashMap使用了一種