Java多線程面試題——查缺補漏

目錄

35.並行與併發有什麼區別?

36.進程與線程的區別?

37.守護線程是什麼?

38.創建線程有哪幾種方式?

39.Runnable 和 Callable 有什麼區別?

40.線程有哪些狀態?

41.sleep( ) 和 wait( )有什麼區別?

42.notify( ) 和 notifyAll( ) 有什麼區別?

43.線程的run( )和start( )有什麼區別?

44.創建線程池有哪些方式?

45.線程池有哪些狀態?

46.線程池中的submit()和execute()有什麼區別?

47.在Java程序中怎麼保證多線程的運行安全?

48.多線程鎖的升級原理是什麼?

49.什麼是死鎖?

50.怎麼防止死鎖?

51.ThreadLocal是什麼?有哪些使用場景?

52.synchronized底層實現原理?

53.synchronized和volatile的區別是什麼?

54.synchronized和Lock有什麼區別?

55.synchronized和ReentrantLock區別是什麼?

56.說一下atomic的原理?


35.並行與併發有什麼區別?

(1)並行是指兩個或者多個事件在同一時刻發生;併發是指兩個或者多個事件在同一時間間隔發生。

(2)並行是在不同實體上的多個事件,併發是在同一實體上的多個事件。

(3)在多臺處理器上同時處理多個任務。在一臺處理器上“同時”處理多個任務。

所以:併發編程的目標是充分的利用處理器的每一個核,以達到最高的處理性能。

36.進程與線程的區別?

(1)進程是程序運行和資源分配的基本單位,一個程序至少有一個進程,一個進程至少有一個線程。進程在執行過程中擁有獨立的內存單元,而多個線程共享內存資源,減少切換次數,從而效率更高。

(2)線程是進程的一個實體,是CPU調度和分派的基本單位,是比程序更小的能獨立運行的基本單位。

(3)同一進程中的多個線程之間可以併發執行。

37.守護線程是什麼?

守護線程(daemon thread),是個服務線程,準確的來說是服務其他的線程。

注意:所有用戶線程一旦結束,守護線程自動結束。

38.創建線程有哪幾種方式?

方式一:繼承 java.lang.Thread類 重寫run方法 創建線程類

(1)定義Thread類的子類,重寫該類的run方法,run方法的方法體就代表線程要完成的任務。因此把run方法稱爲執行體。

(2)創建Thread子類的實例,即創建了線程對象。

(3)調用線程對象的start()方法來啓動該線程。

方式二:實現java.lang.Runnable接口,實現run方法 創建線程類(推薦使用!!)

(1)定義Runnable接口的實現類,並重寫該接口的run方法,該方法的方法體同樣是該線程的線程執行體。

(2)創建Runnable實現類的實例,並以此作爲Thread的target來創建Thread對象,該Thread對象纔是真正的線程對象。

(3)調用線程對象的start( )方法來啓動該線程。

方式三:通過Callable和Future創建線程(有返回值!!)

(1)創建Callable接口的實現類,並實現call( )方法,該方法將作爲線程執行體,並且有返回值

(2)創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了Callable對象的call( )方法的返回值

(3)使用FutureTask對象作爲Thread對象的target創建並啓動新線程

(4)調用FutureTask對象的get( )方法來獲取子線程執行結束後的返回值。

39.Runnable 和 Callable 有什麼區別?

(1)Runnable接口中的run( )方法返回值是void,它做到事情只是純粹去執行run( )方法中的代碼而已

(2)Callable接口中的call( )方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果。

40.線程有哪些狀態?

線程通常有五種狀態,創建、就緒、運行、阻塞、死亡

(1)創建狀態:在生成線程對象,並沒有調用該對象的start方法,這是線程處於創建階段

(2)就緒狀態:當調用了start方法之後,線程就進入了就緒狀態,但是此時線程調度程序還沒有把該線程設置爲當前線程,此時處於就緒狀態。在線程運行之後,從等待或者睡眠中回來之後,也會處於就緒狀態。

(3)運行狀態:線程調度程序將處於就緒狀態的線程設置爲當前狀態,此時線程就進入了運行狀態,開始運行run方法

(4)阻塞狀態:線程正在運行的時候,被暫停,通常是爲了等待某個時間的發生(比如說某項資源就緒)之後再繼續運行。sleep、suspend、wait等方法都可以導致線程阻塞。

(5)死亡狀態:如果一個線程的run方法執行結束或者調用stop方法後,該線程就會死亡。

41.sleep( ) 和 wait( )有什麼區別?

(1)sleep( ):是線程Thread的靜態方法,讓調用線程進入睡眠狀態,讓出執行機會給其他線程,等到休眠時間結束後,線程進入就緒狀態和其他線程一起競爭CPU的執行時間。因爲sleep( )是static方法,它不能改變對象的機鎖,當一個synchronized塊中調用了sleep( )方法,線程雖然進入休眠,但是對象的機鎖沒有被釋放,其他線程依舊無法訪問這個對象

(2)wait( ):是Object類的方法,當一個線程執行到wait方法時,它就進入到一個和該對象相關的等待池,同時釋放對象的機鎖,使得其他線程能夠訪問,可以通過notify( )、notifyAll( )方法來喚醒等待的線程。

42.notify( ) 和 notifyAll( ) 有什麼區別?

notifyAll( )方法:喚醒所有的wait線程;notify( )方法:只隨機喚醒一個wait線程。

(1)如果線程調用了對象的wait( ) 方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖

(2)當有線程調用了對象的notifyAll( )方法或者notify( )方法,被喚醒的線程會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。也就是說,調用了notify後只有一個線程會由等待池進入到鎖池,而notifyAll會將對象池中的所有線程移動到鎖池中,等待鎖競爭。

(3)優先級高的線程競爭到對象鎖的概率大,加入某線程沒有競爭到該對象鎖,它還會留在鎖池中,唯有線程再次調用wait( )後,它纔會重新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,直到執行完了synchronized代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。

43.線程的run( )和start( )有什麼區別?

每個線程都是通過某個特定的Thread對象所對應的方法run( )來完成其操作的,方法run( )稱爲線程體。通過調用Thread類的start( )方法來啓動一個線程。

(1)start( )方法:啓動一個線程,真正的實現多線程運行。這是無需等待run( )方法體代碼執行完畢,可以直接繼續執行下面的代碼;這時此線程時處於就緒狀態的,並沒有運行。然後通過Thread調用方法run( )來完成其運行狀態,這裏的run稱爲線程體,它包含了要執行的這個線程的內容,run方法運行結束,此線程終止。然後CPU再調度其他線程。

(2)run( )方法:是在本線程裏的,只是線程裏的一個函數,而不是多線程的。如果直接調用run( ),其實就相當於調用了一個普通的函數而已,直接調用run()方法必須等待run()方法執行完畢才能執行下面的代碼,所以執行路徑還是隻是一條,根本就沒有線程的特徵,所以在多線程執行時要使用start()而不是run()

44.創建線程池有哪些方式?

(1)newFixedThreadPool( int nThreads ):創建一個固定長度的線程池,每當提交一個任務就創建一個線程,直到達到線程池的最大數量,這時線程規模將不再變化,當線程發生未預期的錯誤而結束時,線程池會補充一個新的線程。

(2)newCachedThreadPool( ):創建一個可緩存的線程池,如果線程池的規模超過了處理需求,將自動回收空閒線程,而當需求增加時,則可以自動添加新線程,線程池的規模不存在任何限制。

(3)newSingleTthreadExecutor( ):這是一個單線程的Executor,它創建單個工作線程來執行任務,如果這個線程異常結束,會創建一個新的來替代它;它的特點是能夠確保依照在隊列中的順序來串行執行。

(4)newScheduledThreadPool( int corePoolSize ):創建一個固定長度的線程池,而且以延遲或定時的方式來執行任務,類似於Timer。

45.線程池有哪些狀態?

五種狀態:Running 、ShutDown 、Stop 、Tidying、Terminated

46.線程池中的submit()和execute()有什麼區別?

(1)接收的參數不同

(2)submit有返回值,而execute沒有

(3)submit方便Exception處理

47.在Java程序中怎麼保證多線程的運行安全?

三個方面體現:

(1)原子性:提供互斥訪問,同一時刻只能有一個線程對數據進行操作。(atomic、synchronized)

(2)可見性:一個線程對主內存的修改可以及時的被其他線程看到。(synchronized、volatile)

(3)有序性:一個程序觀察其他線程中的指令執行順序,由於指令重排序,該觀察結果一般雜亂無序。(happens-before原則)

48.多線程鎖的升級原理是什麼?

在Java中,鎖有四種狀態,級別從低到高依次爲:無狀態鎖、偏向鎖、輕量級鎖、重量級鎖。

注意:這幾個狀態會隨着競爭情況逐漸升級,鎖可以升級但不能降級。

鎖升級的圖示過程:

49.什麼是死鎖?

死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,他們都將無法推進下去,此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。是操作系統層面的一個錯誤,是進程死鎖的簡稱。

50.怎麼防止死鎖?

死鎖的四個必要條件:

(1)互斥條件

(2)請求和保持條件

(3)不可剝奪條件

(4)環路等待條件

打破四個必要條件,即不會發生死鎖。

51.ThreadLocal是什麼?有哪些使用場景?

線程局部變量是侷限於線程內部的變量,屬於線程自身所有,不在多個線程間共享。Java提高ThreadLocal類來支持線程局部變量,是一種實現線程安全的方式。但是在管理環境下(如web服務器)使用線程局部變量的時候要特別小心,在這種情況下,工作線程的生命週期比任何應用變量的生命週期都要長。任何線程局部變量一旦在工作完成後沒有釋放,Java應用就存在內存泄漏的風險。

52.synchronized底層實現原理?

synchronized可以保證方法或者代碼塊在運行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變量的內存可見性。

Java中每一個對象都可以作爲鎖,這是synchronized實現同步的基礎:

(1)普通同步方法:鎖是當前實例對象

(2)靜態同步方法:鎖是當前類的class對象

(3)同步方法塊:鎖是括號裏面的對象

53.synchronized和volatile的區別是什麼?

(1)volatile本質上是在告訴JVM當前變量在寄存器(工作內存)中的值是不確定的,需要從貯存中讀取;synchronized則是鎖定當前變量,只有當前線程可以訪問變量,其他線程被阻塞。

(2)volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、類

(3)volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性

(4)valotile不會造成線程的阻塞;synchronized會造成線程阻塞

(5)volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化。

54.synchronized和Lock有什麼區別?

(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鎖適合代碼少量的同步問題。

55.synchronized和ReentrantLock區別是什麼?

synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。

既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上: 

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

(2)ReentrantLock可以獲取各種鎖的信息

(3)ReentrantLock可以靈活地實現多路通知 

另外,二者的鎖機制其實也是不一樣的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark word。

56.說一下atomic的原理?

Atomic包中的類基本的特性就是在多線程環境下,當有多個線程同時對單個(包括基本類型及引用類型)變量進行操作時,具有排他性,即當多個線程同時對該變量的值進行更新時,僅有一個線程能成功,而未成功的線程可以向自旋鎖一樣,繼續嘗試,一直等到執行成功

Atomic系列的類中的核心方法都會調用unsafe類中的幾個本地方法。我們需要先知道一個東西就是Unsafe類,全名爲:sun.misc.Unsafe,這個類包含了大量的對C代碼的操作,包括很多直接內存分配以及原子操作的調用,而它之所以標記爲非安全的,是告訴你這個裏面大量的方法調用都會存在安全隱患,需要小心使用,否則會導致嚴重的後果,例如在通過unsafe分配內存的時候,如果自己指定某些區域可能會導致一些類似C++一樣的指針越界到其他進程的問題。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章