20 道 Java 高頻面試題 - 題解

作者丨安琪拉的博客

來源丨安琪拉的博客

這裏是更新的 20 道題題解:

  1. 自我介紹一下

    重點介紹自己最滿意的技術,言簡意賅的說最近一段工作經歷和項目,突出項目的貢獻。

 2. 項目中用到的技術棧介紹一下

 3. 項目中做的認爲比較滿意的部分講一講?

提前梳理好,重點介紹自己熟悉和深入研究過的,特別注意,選擇介紹在項目中有真實應用場景的技術,有實際落地的案例是加分項。有的人可能學了很多東西,但是具體一問都沒動手實踐過。

  1. 如果這個技術方案讓你重新設計,你怎麼做?

這個需要在平時工作中就想好,不浮在表面,思考要有深度,你是否對自己負責的系統做過完整的梳理,把整個鏈路、模塊之間的關係都摸清楚了。例如面試官可能會問你,你的系統能承受多大的 QPS、一次業務請求在系統中的完整鏈路,某個模塊的實現邏輯,讓你重新設計有沒有可能做到更好。

  1. Java 集合類都有哪些?平常用到哪些?

安琪拉畫了如下這張圖,我建議大家有時間看看 java.util 的源碼,可以自己動手畫一畫,印象會更深刻。

上面這個圖梳理一下,爲了方便對照上圖看,圖都放在最後

  1. ArrayList 和 LinkedList 區別?

最大的區別: ArrayList 底層是基於動態數組的數據結構,LinkedList 基於鏈表的數據結構;

  1. HashMap 實現的數據結構和擴容過程?

數據結構是數據 + 鏈表

  1. ArrayList 和 LinkedList 區別?你平常怎麼選擇?
  1. 異常類都有哪些?Exception 和 Error 什麼區別?

  1. Synchronized 原理,鎖膨脹過程 ?

圖片

  1. synchronized 和 Reentrantlock 區別?

大家看下面這段,重在理解

兩者的共同點:

  1. ReentrantLock 顯示的獲得、釋放鎖,synchronized 隱式獲得釋放鎖

  2. ReentrantLock 可響應中斷、可輪迴,synchronized 是不可以響應中斷的,爲處理鎖的
    不可用性提供了更高的靈活性

  3. ReentrantLock 是 API 級別的,synchronized 是 JVM 級別的

二者的不同點:

    1. ReentrantLock 可以實現公平鎖

    2. ReentrantLock 通過 Condition 可以綁定多個條件

    3. 底層實現不一樣, synchronized 是同步阻塞,使用的是悲觀併發策略,lock 是同步非阻塞,採用的是樂觀併發策略

    4. Lock 是一個接口,而 synchronized 是 Java 中的關鍵字,synchronized 是內置的語言實現。

    5. synchronized 在發生異常時,會自動釋放線程佔有的鎖,因此不會導致死鎖現象發生;而 Lock 在發生異常時,如果沒有主動通過 unLock() 去釋放鎖,則很可能造成死鎖現象,因此使用 Lock 時需要在 finally 塊中釋放鎖。

    6. Lock 可以讓等待鎖的線程響應中斷,而 synchronized 卻不行,使用 synchronized 時,等待的線程會一直等待下去,不能夠響應中斷。通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。

    7. Lock 可以提高多個線程進行讀操作的效率,既就是實現讀寫鎖等

    作用:都用來協調多線程對共享資源的訪問

    可重入性質:都是可重入鎖,同一線程可以多次獲得同一個鎖

    鎖性質:都保證了可見性和互斥性

    這道題總結解答出處:synchronized 和 ReentrantLock 區別是什麼?

  1. 線程池原理是怎樣的?

  2. 分佈式事務一致性怎麼實現?

這個大家可以優先看自己系統的實現。

我先來介紹一下分佈式事務一致性的需求背景,我們經常使用支付工具轉賬,比如以經典的小明給小紅轉賬 100 元爲例,如果是單機系統,我們可以用本機事務輕鬆解決,但是分佈式系統,轉賬可能是跨系統的調用,我們要保證數據的一致性就有些額外工作量了。

現在普遍互聯網分佈式系統都不會維護實習強一致性,而是做**最終一致性**,允許有短暫的不一致。

我這裏只介紹用的很普遍的事務消息,其他的比如二階段提交、本地消息表做數據一致性比對 & 補償都是實現一致性的方式。

事務消息發送步驟如下:

![](https://mmbiz.qpic.cn/mmbiz_png/SoGf97KLurAqZyRJpw1iaDxjZuFYXZhnyUe5nWa9Hu1tbz0C4rDuvJxjpyxq8UzWVXkkfVSMjbxMia2J0kOQicEcA/640?wx_fmt=png)  

事務消息回查步驟如下:

以上內容參考: RocketMQ 事務消息
  1. 在斷網或者是應用重啓的特殊情況下,上述步驟 4 提交的二次確認最終未到達服務端,經過固定時間後服務端將對該消息發起消息回查。

  2. 發送方收到消息回查後,需要檢查對應消息的本地事務執行的最終結果。

  3. 發送方根據檢查得到的本地事務的最終狀態再次提交二次確認,服務端仍按照步驟 4 對半事務消息進行操作。

  4. 發送方將半事務消息 (類似 prepare 消息),消息內容是將小紅賬號加 100 元,發送至消息隊列 RocketMQ 版服務端。

  5. 消息隊列 RocketMQ 版服務端將消息持久化成功之後,向發送方返回 Ack 確認消息已經發送成功,此時消息爲半事務消息。

  6. 發送方開始執行本地事務邏輯,講小明賬號扣減 100 塊。

  7. 發送方根據本地事務執行結果向服務端提交二次確認(100 塊扣成功則 Commit,100 塊扣失敗則發 Rollback),服務端收到 Commit 狀態則將半事務消息標記爲可投遞,訂閱方最終將收到該消息;服務端收到 Rollback 狀態則刪除半事務消息,訂閱方將不會接受該消息。

  8. 消息亂序遇到過嗎?怎麼解決的?

消息重複業務系統冪等解決,消息亂序,如果是業務系統,一般是 MQ 儘量保障同一用戶路由到同一消息分區,但這個只是儘量,一般消息亂序都是由下游消費方來處理,處理方法是消息中增加版本號、occurTime(業務時間發生時間) 來判斷消息的先後順序,然後做對應的業務邏輯,例如,同一業務流水號,從庫裏面的數據的版本號或 occurTime 和新消息的版本號和 occurTime 比較,版本號更大,時間更靠後的爲最新消息,可以做更新操作。  

一般消息中間件都會遇到以下幾個問題:
  1. ThreadLocal 用過嗎?實現機制?
發現很多博客關於 ThreadLocal 的說明寫錯了,ThreadLocal 不是維護了 key 爲 Thread 對象的 Map,而是 Thread 對象維護了一個 key 爲 ThreadLocal 對象的 Map。

參考之前寫的:一文了解 ThreadLocal 用法
  1. wait、sleep 區別?
1、sleep 是線程中的方法,但是 wait 是 Object 中的方法。

2、sleep 方法不會釋放 lock,但是 wait 會釋放,而且會加入到等待隊列中。

3、sleep 方法不依賴於同步器 synchronized,但是 wait 需要依賴 synchronized 關鍵字。

4、sleep 不需要被喚醒(休眠之後退出阻塞),但是 wait 需要(不指定時間需要被別人中斷)。
  1. 反射用過嗎?什麼原理?

  2. 動態代理瞭解嗎?

  3. 單例模式瞭解嗎?實現一個線程安全的單例模式?

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。

這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。

**注意:**

實現代碼(只列舉雙重檢查鎖的寫法,其他寫法參考):

![](https://mmbiz.qpic.cn/mmbiz_png/SoGf97KLurAqZyRJpw1iaDxjZuFYXZhnyBCVA3NAIgRtxBwlBsnE93v2xbEcJCTzWojCLmJSfy4vCyUIsuhqu1w/640?wx_fmt=png)
  1. 無界隊列和有界隊列?

先說概念:

有界隊列

有界隊列:就是有固定大小的隊列。比如設定了固定大小的 LinkedBlockingQueue,又或者大小爲 0,只是在生產者和消費者中做中轉用的 SynchronousQueue。

無界隊列

無界隊列:指的是沒有設置固定大小的隊列。這些隊列的特點是可以直接入列,直到溢出。當然現實使用中,幾乎不會有到這麼大的容量(超過 Integer.MAX_VALUE),所以從使用者的體驗上,就相當於 “無界”。比如沒有設定固定大小的 LinkedBlockingQueue。

無界隊列的特性:所以無界隊列的特點就是可以一直入列,不存在隊列滿負荷的現象。這個特性,在我們自定義線程池的使用中非常容易出錯。而出錯的根本原因是對線程池內部原理的不瞭解。

使用無界隊列創建了一個線程池如下:

ExecutorService executor =  new ThreadPoolExecutor(2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

這裏的隊列都指線程池使用的阻塞隊列 BlockingQueue 的實現,使用的最多的應該是 LinkedBlockingQueue,注意一般情況下要配置一下隊列大小,設置成有界隊列,否則 JVM 內存會被撐爆!

來一張隊列全體照

1. 常見的有界隊列

上面三個隊列的共性:

ArrayBlockingQueue 與 LinkedBlockingQueue 對比:

2. 常見的無界隊列

無界隊列的共性:

此題題解參數: 

https://www.yuque.com/tiankongyiwusuoyouweihegeiwoanwei/kb/ud3nbr

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/hmDOma_kTTH3xcdpKZuCzQ