Java面試題!附詳解答案

Java作為最受歡迎的編程語言想必是很受小夥伴們青睞的,今天浙江優就業

的小U老師為大家整離了Java的面試題喲~

Java面試題!附詳解答案

關於垃圾回收

1、簡單的解釋一下垃圾回收

Java 垃圾回收機制最基本的做法是分代回收。

內存中的區域被劃分成不同的世代,對象根據其存活的時間被保存在對應世代的區域中。

一般的實現是劃分成3個世代:年輕、年老和永久。

內存的分配是發生在年輕世代中的。

當一個對象存活時間足夠長的時候,它就會被複制到年老世代中。

對於不同的世代可以使用不同的垃圾回收算法。

進行世代劃分的出發點是對應用中對象存活時間進行研究之後得出的統計規律。

一般來說,一個應用中的大部分對象的存活時間都很短。

比如局部變量的存活時間就只在方法的執行過程中。

基於這一點,對於年輕世代的垃圾回收算法就可以很有針對性。

2、你知道哪些垃圾回收算法?

垃圾回收從理論上非常容易理解,具體的方法有以下幾種:

1). 標記-清除

2). 標記-複製

3). 標記-整理

3、如何判斷一個對象是否應該被回收

這就是所謂的對象存活性判斷。

常用的方法有兩種:

1). 引用計數法;

2). 對象可達性分析。

由於引用計數法存在互相引用導致無法進行GC的問題,所以目前JVM虛擬機多使用對象可達性分析算法。

進程,線程相關

1、說說進程,線程,協程之間的區別

簡而言之,進程是程序運行和資源分配的基本單位,一個程序至少有一個進程,一個進程至少有一個線程。

進程在執行過程中擁有獨立的內存單元,而多個線程共享內存資源,減少切換次數,從而效率更高。

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

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

2、創建兩種線程的方式?他們有什麼區別?

通過實現java.lang.Runnable或者通過擴展java.lang.Thread類。

相比擴展Thread,實現Runnable接口可能更優。

原因有二:

1). Java不支持多繼承.因此擴展Thread類就代表這個子類不能擴展其他類。

而實現Runnable接口的類還可能擴展另一個類。

2). 類可能只要求可執行即可,因此集成整個Thread類的開銷過大。

3、Runnable和Callable的區別

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

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

這其實是很有用的一個特性,因為多線程相比單線程更難、更復雜的一個重要原因就是因為多線程充滿著未知性,

某條線程是否執行了?

某條線程執行了多久?

某條線程執行的時候我們期望的數據是否已經賦值完畢?

無法得知,我們能做的只是等待這條多線程的任務執行完畢而已。

而Callable+Future/FutureTask卻可以獲取多線程運行的結果,可以在等待時間太長沒獲取到需要的數據的情況下取消該線程的任務,真的是非常有用。

4、你瞭解守護線程嗎?它和非守護線程有什麼區別

程序運行完畢,jvm會等待非守護線程完成後關閉,但是jvm不會等待守護線程。

守護線程最典型的例子就是GC線程。

5、什麼是多線程上下文切換

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

6、什麼導致線程阻塞

阻塞指的是暫停一個線程的執行以等待某個條件發生(如某資源就緒),學過操作系統的同學對它一定已經很熟悉了。

Java 提供了大量方法來支持阻塞,下面讓我們逐一分析。

方法說明sleep()sleep() 允許 指定以毫秒為單位的一段時間作為參數,它使得線程在指定的時間內進入阻塞狀態,不能得到CPU 時間,指定的時間一過,線程重新進入可執行狀態。 典型地,sleep() 被用在等待某個資源就緒的情形:測試發現條件不滿足後,讓線程阻塞一段時間後重新測試,直到條件滿足為止

suspend()和resume()

兩個方法配套使用,suspend()使得線程進入阻塞狀態,並且不會自動恢復,必須其對應的resume() 被調用,才能使得線程重新進入可執行狀態。典型地,suspend() 和 resume() 被用在等待另一個線程產生的結果的情形:測試發現結果還沒有產生後,讓線程阻塞,另一個線程產生了結果後,調用 resume() 使其恢復。yield()yield() 使得線程放棄當前分得的 CPU 時間,但是不使線程阻塞,即線程仍處於可執行狀態,隨時可能再次分得 CPU 時間。調用 yield() 的效果等價於調度程序認為該線程已執行了足夠的時間從而轉到另一個線程

wait()和notify()

兩個方法配套使用,wait() 使得線程進入阻塞狀態,它有兩種形式,一種允許 指定以毫秒為單位的一段時間作為參數,另一種沒有參數,前者當對應的 notify() 被調用或者超出指定時間時線程重新進入可執行狀態,後者則必須對應的 notify() 被調用.

50wait(),notify()和suspend(),resume()之間的區別

初看起來它們與 suspend() 和 resume() 方法對沒有什麼分別,但是事實上它們是截然不同的。

區別的核心在於,前面敘述的所有方法,阻塞時都不會釋放佔用的鎖(如果佔用了的話),而這一對方法則相反。

上述的核心區別導致了一系列的細節上的區別。

首先,

前面敘述的所有方法都隸屬於 Thread 類,但是這一對卻直接隸屬於 Object 類,也就是說,所有對象都擁有這一對方法。

初看起來這十分不可思議,但是實際上卻是很自然的,因為這一對方法阻塞時要釋放佔用的鎖,

而鎖是任何對象都具有的,調用任意對象的 wait() 方法導致線程阻塞,並且該對象上的鎖被釋放。

而調用 任意對象的notify()方法則導致因調用該對象的 wait() 方法而阻塞的線程中隨機選擇的一個解除阻塞(但要等到獲得鎖後才真正可執行)。

其次,

前面敘述的所有方法都可在任何位置調用,但是這一對方法卻必須在 synchronized 方法或塊中調用,理由也很簡單,只有在synchronized 方法或塊中當前線程才佔有鎖,才有鎖可以釋放。

同樣的道理,調用這一對方法的對象上的鎖必須為當前線程所擁有,這樣才有鎖可以釋放。

因此,這一對方法調用必須放置在這樣的 synchronized 方法或塊中,該方法或塊的上鎖對象就是調用這一對方法的對象。

若不滿足這一條件,則程序雖然仍能編譯,但在運行時會出現IllegalMonitorStateException 異常。

wait() 和 notify() 方法的上述特性決定了它們經常和synchronized 方法或塊一起使用,

將它們和操作系統的進程間通信機制作一個比較就會發現它們的相似性:

synchronized方法或塊提供了類似於操作系統原語的功能,它們的執行不會受到多線程機制的干擾,

它們的結合使得我們可以實現操作系統上一系列精妙的進程間通信的算法(如信號量算法),並用於解決各種複雜的線程間通信問題。

關於 wait() 和 notify() 方法最後再說明兩點:

1). 調用 notify() 方法導致解除阻塞的線程是從因調用該對象的 wait() 方法而阻塞的線程中隨機選取的,

我們無法預料哪一個線程將會被選擇,所以編程時要特別小心,避免因這種不確定性而產生問題。

2). 除了 notify(),還有一個方法 notifyAll() 也可起到類似作用,唯一的區別在於,調用 notifyAll() 方法將把因調用該對象的 wait() 方法而阻塞的所有線程一次性全部解除阻塞。

當然,只有獲得鎖的那一個線程才能進入可執行狀態。

談到阻塞,就不能不談一談死鎖,略一分析就能發現,suspend() 方法和不指定超時期限的 wait() 方法的調用都可能產生死鎖。

遺憾的是,Java 並不在語言級別上支持死鎖的避免,我們在編程中必須小心地避免死鎖。

以上我們對 Java 中實現線程阻塞的各種方法作了一番分析,我們重點分析了 wait() 和 notify() 方法,

因為它們的功能最強大,使用也最靈活,但是這也導致了它們的效率較低,較容易出錯。

實際使用中我們應該靈活使用各種方法,以便更好地達到我們的目的。

50為什麼 wait() 方法和 notify()/notifyAll() 方法要在同步塊中被調用

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

51wait() 方法和 notify()/notifyAll() 方法在放棄對象監視器時有什麼區別

wait() 方法和 notify()/notifyAll()方法在放棄對象監視器的時候的區別在於:

wait()方法立即釋放對象監視器,notify()/notifyAll()方法則會等待線程剩餘代碼執行完畢才會放棄對象監視器。


分享到:


相關文章: