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,不僅提供線程安全還用鎖分離和內部分區等現代技術提高了可擴展性
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章