Java面試集錦:25道線程類相關面試題與答案(一)

Java面試集錦:25道線程類相關面試題與答案(一)

Java面試集錦:25道線程類相關面試題與答案(一)


1. 線程是什麼?進程是什麼?二者有什麼區別和聯繫?

(1)線程是CPU獨立運行和獨立調度的基本單位;
(2)進程是資源分配的基本單位;是執行著的應用程序
兩者的聯繫:進程和線程都是操作系統所運行的程序運行的基本單元。

區別:
(1)進程具有獨立的空間地址,一個進程崩潰後,在保護模式下不會對其它進程產生影響。
(2)線程只是一個進程的不同執行路徑,線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉。

進程是執行著的應用程序,而線程是進程內部的一個執行序列。一個進程可以有多個線程。線程又叫做輕量級進程。

2. 線程和進程各自有什麼區別和優劣呢?

進程是資源分配的最小單位,線程是程序執行的最小單位。

進程有自己的獨立地址空間,每啟動一個進程,系統就會為它分配地址空間,建立數據表來維護代碼段、堆棧段和數據段,這種操作非常昂貴。而線程是共享進程中的數據的,使用相同的地址空間,因此CPU切換一個線程的花費遠比進程要小很多,同時創建一個線程的開銷也比進程要小很多。

線程之間的通信更方便,同一進程下的線程共享全局變量、靜態變量等數據,而進程之間的通信需要以通信的方式(IPC)進行。不過如何處理好同步與互斥是編寫多線程程序的難點。

但是多進程程序更健壯,多線程程序只要有一個線程死掉,整個進程也死掉了,而一個進程死掉並不會對另外一個進程造成影響,因為進程有自己獨立的地址空間。

可參考文章[]()

3. 創建線程有幾種不同的方式?你喜歡哪一種?為什麼?

有三種方式可以用來創建線程:

繼承Thread類

實現Runnable接口

應用程序可以使用Executor框架來創建線程池

實現Runnable接口這種方式更受歡迎,因為這不需要繼承Thread類。在應用設計中已經繼承了別的對象的情況下,這需要多繼承(而Java不支持多繼承),只能實現接口。同時,線程池也是非常高效的,很容易實現和使用。

4. 概括的解釋下線程的幾種可用狀態?

線程在執行過程中,可以處於下面幾種狀態:

就緒(Runnable):線程準備運行,不一定立馬就能開始執行。

運行中(Running):進程正在執行線程的代碼。

等待中(Waiting):線程處於阻塞的狀態,等待外部的處理結束。

睡眠中(Sleeping):線程被強制睡眠。

I/O阻塞(Blocked on I/O):等待I/O操作完成。

同步阻塞(Blocked on Synchronization):等待獲取鎖。

死亡(Dead):線程完成了執行。

5. 同步方法和同步代碼塊的區別是什麼?

在Java語言中,每一個對象有一把鎖。線程可以使用synchronized關鍵字來獲取對象上的鎖。synchronized關鍵字可應用在方法級別(粗粒度鎖)或者是代碼塊級別(細粒度鎖)。

Java語言的關鍵字,當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多隻有一個線程執行該段代碼。

(1)、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。

(2)、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。

(3)、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。

(4)、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。

(5)、以上規則對其它對象鎖同樣適用。

6. 在監視器(Monitor)內部,是如何做線程同步的?程序應該做哪種級別的同步?

監視器和鎖在Java虛擬機中是一塊使用的。監視器監視一塊同步代碼塊,確保一次只有一個線程執行同步代碼塊。每一個監視器都和一個對象引用相關聯。線程在獲取鎖之前不允許執行同步代碼。

7. 什麼是死鎖(deadlock)?

兩個進程都在等待對方執行完畢才能繼續往下執行的時候就發生了死鎖。結果就是兩個進程都陷入了無限的等待中。

8. 如何確保N個線程可以訪問N個資源同時又不導致死鎖?

使用多線程的時候,一種非常簡單的避免死鎖的方式就是:指定獲取鎖的順序,並強制線程按照指定的順序獲取鎖。因此,如果所有的線程都是以同樣的順序加鎖和釋放鎖,就不會出現死鎖了。

9. 如何避免死鎖?

多線程產生死鎖的四個必要條件:
互斥條件: 一個資源每次只能被一個進程使用。
保持和請求條件: 一個進程因請求資源而阻塞時,對已獲得資源保持不放。
不可剝奪調教: 進程已獲得資源,在未使用完成前,不能被剝奪。
循環等待條件:

若干進程之間形成一種頭尾相接的循環等待資源關係。

只要破壞其中任意一個條件,就可以避免死鎖,其中最簡單的就是破環循環等待條件。按同一順序訪問對象,加載鎖,釋放鎖。

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

start()方法被用來啟動新創建的線程,使該被創建的線程狀態變為可運行狀態。當你調用run()方法的時候,只會是在原來的線程中調用,沒有新的線程啟動,start()方法才會啟動新線程。如果我們調用了Thread的run()方法,它的行為就會和普通的方法一樣,直接運行run()方法。為了在新的線程中執行我們的代碼,必須使用Thread.start()方法。

11. Java中Runnable和Callable有什麼不同?

Runnable和Callable都代表那些要在不同的線程中執行的任務。Runnable從JDK1.0開始就有了,Callable是在JDK1.5增加的。它們的主要區別是Callable的 call() 方法可以返回值和拋出異常,而Runnable的run()方法沒有這些功能。Callable可以返回裝載有計算結果的Future對象。

12. Java中什麼是競態條件?

在大多數實際的多線程應用中,兩個或兩個以上的線程需要共享對同一數據的存取。如果i線程存取相同的對象,並且每一個線程都調用了一個修改該對象狀態的方法,將會發生什麼呢?可以想象,線程彼此踩了對方的腳。根據線程訪問數據的次序,可能會產生訛誤的對象。這樣的情況通常稱為競爭條件。

13. Java中如何停止一個線程?

Java提供了很豐富的API但沒有為停止線程提供API。
JDK 1.0本來有一些像stop(), suspend() 和 resume()的控制方法,但是由於潛在的死鎖威脅。
因此在後續的JDK版本中他們被棄用了,之後Java API的設計者就沒有提供一個兼容且線程安全的方法來停止一個線程。
當run()或者 call() 方法執行完的時候線程會自動結束,如果要手動結束一個線程,可以用volatile 布爾變量來退出run()方法的循環或者是取消任務來中斷線程。

14. Java中notify 和 notifyAll有什麼區別?

一個很明顯的原因是JAVA提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。


如果線程需要等待某些鎖那麼調用對象中的wait()方法就有意義了。如果wait()方法定義在Thread類中,線程正在等待的是哪個鎖就不明顯了。簡單的說,由於wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中因為鎖屬於對象。

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

同步集合與併發集合都為多線程和併發提供了合適的線程安全的集合,不過併發集合的可擴展性更高。
在Java1.5之前程序員們只有同步集合來用且在多線程併發的時候會導致爭用,阻礙了系統的擴展性。Java5介紹了併發集合像ConcurrentHashMap,不僅提供線程安全還用鎖分離和內部分區等現代技術提高了可擴展性。

16. 什麼是線程池?

線程池是一種多線程處理形式,處理過程中將任務提交到線程池,任務的執行交由線程池來管理。
如果每個請求都創建一個線程去處理,那麼服務器的資源很快就會被耗盡,使用線程池可以減少創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。

17. 為什麼要使用線程池?

創建線程和銷燬線程的花銷是比較大的,這些時間有可能比處理業務的時間還要長。這樣頻繁的創建線程和銷燬線程,再加上業務工作線程,消耗系統資源的時間,可能導致系統資源不足。(我們可以把創建和銷燬的線程的過程去掉)

18. 線程池有什麼作用?

線程池作用就是限制系統中執行線程的數量。

1、提高效率 創建好一定數量的線程放在池中,等需要使用的時候就從池中拿一個,這要比需要的時候創建一個線程對象要快的多。

2、方便管理 可以編寫線程池管理代碼對池中的線程同一進行管理,比如說啟動時有該程序創建100個線程,每當有請求的時候,就分配一個線程去工作,如果剛好併發有101個請求,那多出的這一個請求可以排隊等候,避免因無休止的創建線程導致系統崩潰。

19. 說說幾種常見的線程池及使用場景?

1、newSingleThreadExecutor創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

2、newFixedThreadPool創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。

3、newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。

4、newScheduledThreadPool創建一個定長線程池,支持定時及週期性任務執行。

20. 線程池中的幾種重要的參數?

corePoolSize就是線程池中的核心線程數量,這幾個核心線程,只是在沒有用的時候,也不會被回收

maximumPoolSize就是線程池中可以容納的最大線程的數量

keepAliveTime,就是線程池中除了核心線程之外的其他的最長可以保留的時間,因為在線程池中,除了核心線程即使在無任務的情況下也不能被清除,其餘的都是有存活時間的,意思就是非核心線程可以保留的最長的空閒時間。


util,就是計算這個時間的一個單位。

workQueue,就是等待隊列,任務可以儲存在任務隊列中等待被執行,執行的是FIFIO原則(先進先出)。

threadFactory,就是創建線程的線程工廠。

handler,是一種拒絕策略,我們可以在任務滿了之後,拒絕執行某些任務。

21. 說說線程池的拒絕策略?

當請求任務不斷的過來,而系統此時又處理不過來的時候,我們需要採取的策略是拒絕服務。RejectedExecutionHandler接口提供了拒絕任務處理的自定義方法的機會。在ThreadPoolExecutor中已經包含四種處理策略。

  • AbortPolicy策略:該策略會直接拋出異常,阻止系統正常工作。
  • CallerRunsPolicy 策略:只要線程池未關閉,該策略直接在調用者線程中,運行當前的被丟棄的任務。
  • DiscardOleddestPolicy策略: 該策略將丟棄最老的一個請求,也就是即將被執行的任務,並嘗試再次提交當前任務。
  • DiscardPolicy策略:該策略默默的丟棄無法處理的任務,不予任何處理。

除了JDK默認提供的四種拒絕策略,我們可以根據自己的業務需求去自定義拒絕策略,自定義的方式很簡單,直接實現RejectedExecutionHandler接口即可。

22. execute和submit的區別?

我們執行任務是用的execute方法,除了execute方法,還有一個submit方法也可以執行我們提交的任務。

這兩個方法有什麼區別呢?分別適用於在什麼場景下呢?
我們來做一個簡單的分析。

  • execute適用於不需要關注返回值的場景,只需要將線程丟到線程池中去執行就可以了。
  • submit方法適用於需要關注返回值的場景

23. 五種線程池的使用場景?

  • newSingleThreadExecutor:一個單線程的線程池,可以用於需要保證順序執行的場景,並且只有一個線程在執行。
  • newFixedThreadPool:一個固定大小的線程池,可以用於已知併發壓力的情況下,對線程數做限制。
  • newCachedThreadPool:一個可以無限擴大的線程池,比較適合處理執行時間比較小的任務。
  • newScheduledThreadPool:可以延時啟動,定時啟動的線程池,適用於需要多個後臺線程執行週期任務的場景。
  • newWorkStealingPool:一個擁有多個任務隊列的線程池,可以減少連接數,創建當前可用cpu數量的線程來並行執行。

24. 線程池如何關閉? 初始化線程池時線程數的選擇?

關閉線程池可以調用shutdownNow和shutdown兩個方法來實現

shutdownNow:對正在執行的任務全部發出interrupt(),停止執行,對還未開始執行的任務全部取消,並且返回還沒開始的任務列表。

shutdown:當我們調用shutdown後,線程池將不再接受新的任務,但也不會去強制終止已經提交或者正在執行中的任務。

如果任務是IO密集型,一般線程數需要設置2倍CPU數以上,以此來儘量利用CPU資源。

如果任務是CPU密集型,一般線程數量只需要設置CPU數加1即可,更多的線程數也只能增加上下文切換,不能增加CPU利用率。

上述只是一個基本思想,如果真的需要精確的控制,還是需要上線以後觀察線程池中線程數量跟隊列的情況來定。

25. 線程池都有哪幾種工作隊列?

1、ArrayBlockingQueue

是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。

2、LinkedBlockingQueue
一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列

3、SynchronousQueue

一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。

4、PriorityBlockingQueue

一個具有優先級的無限阻塞隊列。

推薦

大廠筆試內容集合(內有詳細解析) 持續更新中….

ProcessOn是一個在線作圖工具的聚合平臺~

文末

歡迎關注個人微信公眾號:Coder編程
歡迎關注Coder編程公眾號,主要分享數據結構與算法、Java相關知識體系、框架知識及原理、Spring全家桶、微服務項目實戰、DevOps實踐之路、每日一篇互聯網大廠面試或筆試題以及PMP項目管理知識等。更多精彩內容正在路上~

Java面試集錦:25道線程類相關面試題與答案(一)


分享到:


相關文章: