下面 下麪列出這份 Java 面試問題列表包含的主題
多執行緒,併發及執行緒基礎
數據型別轉換的基本原則
垃圾回收(GC)
Java 集合框架
陣列
字串
GOF 設計模式
SOLID
抽象類與介面
Java 基礎,如 equals 和 hashcode
泛型與列舉
Java IO 與 NIO
常用網路協定
Java 中的數據結構和演算法
正則表達式
JVM 底層
Java 最佳實踐
JDBC
Date, Time 與 Calendar
Java 處理 XML
JUnit
程式設計
現在是時候給你展示我近 5 年從各種面試中收集來的 133 個問題了。我確定你在自己的面試中見過很多這些問題,很多問題你也能正確回答。
能,Java 中可以建立 volatile 型別陣列,不過只是一個指向陣列的參照,而不是整個陣列。我的意思是,如果改變參照指向的陣列,將會受到 volatile 的保護,但是如果多個執行緒同時改變陣列的元素,volatile 標示符就不能起到之前的保護作用了。
一個典型的例子是在類中有一個 long 型別的成員變數。如果你知道該成員變數會被多個執行緒存取,如計數器、價格等,你最好是將其設定爲 volatile。爲什麼?因爲 Java 中讀取 long 型別變數不是原子的,需要分成兩步,如果一個執行緒正在修改該 long 變數的值,另一個執行緒可能只能看到該值的一半(前 32 位)。但是對一個 volatile 型的 long 或 double 變數的讀寫是原子。
一種實踐是用 volatile 修飾 long 和 double 變數,使其能按原子型別來讀寫。double 和 long 都是 64 位寬,因此對這兩種型別的讀是分爲兩部分的,第一次讀取第一個 32 位,然後再讀剩下的 32 位,這個過程不是原子的,但 Java 中volatile 型的 long 或 double 變數的讀寫是原子的。volatile 修復符的另一個作用是提供記憶體屏障(memory barrier),例如在分佈式框架中的應用。簡單的說,就是當你寫一個 volatile 變數之前,Java 記憶體模型會插入一個寫屏障(writebarrier),讀一個 volatile 變數之前,會插入一個讀屏障(read barrier)。意思就是說,在你寫一個 volatile 域時,能保證任何執行緒都能看到你寫的值,同時,
在寫之前,也能保證任何數值的更新對所有執行緒是可見的,因爲記憶體屏障會將其他所有寫的值更新到快取。
volatile 變數提供順序和可見性保證,例如,JVM 或者 JIT 爲了獲得更好的效能會對語句重排序,但是 volatile 型別變數即使在沒有同步塊的情況下賦值也不會與其他語句重排序。 volatile 提供 happens-before 的保證,確保一個執行緒的修改能對其他執行緒是可見的。某些情況下,volatile 還能提供原子性,如讀 64 位數據型別,像 long 和 double 都不是原子的,但 volatile 型別的 double 和long 就是原子的。
從寫程式碼的角度來說,兩者的複雜度是相同的,因爲同步程式碼與執行緒數量是相互獨立的。但是同步策略的選擇依賴於執行緒的數量,因爲越多的執行緒意味着更大的競爭,所以你需要利用同步技術,如鎖分離,這要求更復雜的程式碼和專業知識。
wait() 方法應該在回圈呼叫,因爲當執行緒獲取到 CPU 開始執行的時候,其他條件可能還沒有滿足,所以在處理前,回圈檢測條件是否滿足會更好。下面 下麪是一段標準的使用 wait 和 notify 方法的程式碼:
// The standard idiom for using the wait method
synchronized (obj) {
while (condition does not hold)
obj.wait(); // (Releases lock, and reacquires on wakeup)
... // Perform action appropriate to condition
}
參見 [Effective Java]第 69 條,獲取更多關於爲什麼應該在回圈中來呼叫 wait方法的內容。
僞共用是多執行緒系統(每個處理器有自己的區域性快取)中一個衆所周知的效能問題。僞共用發生在不同處理器的上的執行緒對變數的修改依賴於相同的快取行,如下圖所示:
有經驗程式設計師的 Java 面試題
僞共用問題很難被發現,因爲執行緒可能存取完全不同的全域性變數,記憶體中卻碰巧在很相近的位置上。如其他諸多的併發問題,避免僞共用的最基本方式是仔細審查程式碼,根據快取行來調整你的數據結構。
Busy spin 是一種在不釋放 CPU 的基礎上等待事件的技術。它經常用於避免丟失 CPU 快取中的數據(如果執行緒先暫停,之後在其他 CPU 上執行就會丟失)。所以,如果你的工作要求低延遲,並且你的執行緒目前沒有任何順序,這樣你就可以通過回圈檢測佇列中的新訊息來代替呼叫 sleep() 或 wait() 方法。它唯一的好處就是你只需等待很短的時間,如幾微秒或幾納秒。LMAX 分佈式框架是一個高效能執行緒間通訊的庫,該庫有一個 BusySpinWaitStrategy 類就是基於這個概念實現的,使用 busy spin 回圈 EventProcessors 等待屏障。
在 Linux 下,你可以通過命令 kill -3 PID (Java 進程的進程 ID)來獲取 Java應用的 dump 檔案。在 Windows 下,你可以按下 Ctrl + Break 來獲取。這樣 JVM 就會將執行緒的 dump 檔案列印到標準輸出或錯誤檔案中,它可能列印在控制檯或者日誌檔案中,具體位置依賴應用的設定。如果你使用 Tomcat。
不是,Swing 不是執行緒安全的。你不能通過任何執行緒來更新 Swing 元件,如JTable、JList 或 JPanel,事實上,它們只能通過 GUI 或 AWT 執行緒來更新。這就是爲什麼 Swing 提供 invokeAndWait() 和 invokeLater() 方法來獲取其他執行緒的 GUI 更新請求。這些方法將更新請求放入 AWT 的執行緒佇列中,可以一直等待,也可以通過非同步更新直接返回結果。你也可以在參考答案中檢視和學習到更詳細的內容。
執行緒區域性變數是侷限於執行緒內部的變數,屬於執行緒自身所有,不在多個執行緒間共用。Java 提供 ThreadLocal 類來支援執行緒區域性變數,是一種實現執行緒安全的方式。但是在管理環境下(如 web 伺服器)使用執行緒區域性變數的時候要特別小心,在這種情況下,工作執行緒的生命週期比任何應用變數的生命週期都要長。任何執行緒區域性變數一旦在工作完成後沒有釋放,Java 應用就存在記憶體泄露的風險。
答案
http://java67.blogspot.sg/2012/12/producer-consumer-problem-with-wait-and-notify-example.html
請參考答案中的範例程式碼。只要記住在同步塊中呼叫 wait() 和 notify()方法,如果阻塞,通過回圈來測試等待條件。
答案
http://javarevisited.blogspot.in/2012/12/how-to-create-thread-safe-singleton-in-java-example.html
請參考答案中的範例程式碼,這裏面一步一步教你建立一個執行緒安全的 Java 單例類。當我們說執行緒安全時,意思是即使初始化是在多執行緒環境中,仍然能保證單個範例。Java 中,使用列舉作爲單例類是最簡單的方式來建立執行緒安全單例模式
的方式。
雖然兩者都是用來暫停當前執行的執行緒,但是 sleep() 實際上只是短暫停頓,因爲它不會釋放鎖,而 wait() 意味着條件等待,這就是爲什麼該方法要釋放鎖,因爲只有這樣,其他等待的執行緒才能 纔能在滿足條件時獲取到該鎖。
不可變物件指物件一旦被建立,狀態就不能再改變。任何修改都會建立一個新的物件,如 String、Integer 及其它包裝類。詳情參見答案,一步一步指導你在 Java中建立一個不可變的類。
是的,我們是可以建立一個包含可變物件的不可變物件的,你只需要謹慎一點,不要共用可變物件的參照就可以了,如果需要變化時,就返回原物件的一個拷貝。最常見的例子就是物件中包含一個日期物件的參照。
如果不是特別關心記憶體和效能的話,使用 BigDecimal,否則使用預定義精度的double 型別。
可以使用 String 接收 byte[] 參數的構造器來進行轉換,需要注意的點是要使用的正確的編碼,否則會使用平臺預設編碼,這個編碼可能跟原來的編碼相同,也可能不同。
這個問題你來回答 :-)
是的,我們可以做強制轉換,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,所以,如果強制轉化是,int 型別的高 24 位將會被丟棄,byte 型別的範圍是從 -128 到 128。
答案
http://javarevisited.blogspot.sg/2012/12/what-is-type-casting-in-java-class-interface-example.html
java.lang.Cloneable 是一個標示性介面,不包含任何方法,clone 方法在object 類中定義。並且需要知道 clone() 方法是一個本地方法,這意味着它是由c 或 c++ 或 其他本地語言實現的。
答案:
不是執行緒安全的操作。它涉及到多個指令,如讀取變數值,增加,然後儲存回記憶體,這個過程可能會出現多個執行緒交差。
+= 隱式的將加操作的結果型別強制轉換爲持有結果的型別。如果兩這個整型相加,如 byte、short 或者 int,首先會將它們提升到 int 型別,然後在執行加法操作。如果加法操作的結果比 a 的最大值要大,則 a+b 會出現編譯錯誤,但是a += b 沒問題,如下:
byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok
(譯者注:這個地方應該表述的有誤,其實無論 a+b 的值爲多少,編譯器都會報錯,因爲 a+b 操作會將 a、b 提升爲 int 型別,所以將 int 型別賦值給 byte就會編譯出錯)
不行,你不能在沒有強制型別轉換的前提下將一個 double 值賦值給 long 型別的變數,因爲 double 型別的範圍比 long 型別更廣,所以必須要進行強制轉換。
false,因爲有些浮點數不能完全精確的表示出來。
Integer 物件會佔用更多的記憶體。Integer 是一個物件,需要儲存物件的元數據。但是 int 是一個原始型別的數據,所以佔用的空間更少。
Java 中的 String 不可變是因爲 Java 的設計者認爲字串使用非常頻繁,將字串設定爲不可變可以允許多個用戶端之間共用相同的字串。
從 Java 7 開始,我們可以在 switch case 中使用字串,但這僅僅是一個語法糖。內部實現在 switch 中使用字串的 hash code。
當你從一個構造器中呼叫另一個構造器,就是 Java 中的構造器鏈。這種情況只在過載了類的構造器的時候纔會出現。