由於個人發展的原因,前段時間又出去面試了,這次面試目標比較清晰,主要面一些業務量比較大、業務比較核心的部門。
前前後後面了一個多月,面了不少公司,面試輪次二三十輪應該是有的。
按照自己的習慣,將這次面試過程中的一些經驗總結、心得體會記錄下來,自己留個記錄,也希望可以幫助到一些同學。
幾個關鍵詞:Java後端、5年經驗、普通本科、面試時在阿里。
B站、愛奇藝:投了簡歷沒下文,扎心了
字節:3輪技術 + hr,offer
快手:4輪技術 + hr,offer
美團:3~5輪技術 + hr ,3個部門offer(可並行面多部門)
途虎:3輪技術 + hr + CTO,offer
得物:1面通過,2面需要去現場(上海),放棄
貝殼:1面通過,2面面試官表示業務並發量不大,放棄
猿輔導:hr表示只能在19點之前面試,19點後已經下班(過分了),放棄
近期也在開始準備剩餘部分的面試題解析,redis 和 spring+mybatis 已經在路上,有興趣的可以關注一波,不過首先還是優先保證質量,所以可能效率不會那麼高。
這邊列舉幾個我印象比較深的題目。
ConcurrentHashMap的並發擴容
ConcurrentHashMap 在擴容時會計算出一個步長(stride),最小值是16,然後給當前擴容線程分配「一個步長」的節點數,例如16個,讓該線程去對這16個節點進行擴容操作(將節點從老表移動到新表)。
如果在擴容結束前又來一個線程,則也會給該線程分配一個步長的節點數讓該線程去擴容。依次類推,以達到多線程並發擴容的效果。
AQS中有多個線程並發添加到隊列中,需要做並發控制嗎
這個是需要的,源碼中是通過 CAS 來進行並發控制。
在 addWaiter(Node mode) 方法中,如果並發添加節點,則只會有一個線程成功,另一個會失敗,從而走到 enq(node) 方法中去進行重試。
說實話,雖然看過源碼,但是真的很難記得這麼多細節,因為 Java 面試要準備的東西實在太多了,所以這邊可以利用一個小技巧。
小技巧:首先並發控制肯定是要做的,這個不難推測;接著,如果注意觀察的話,不難發現在 JDK 源碼中做並發控制的方式大多是 CAS,所以當你不知道怎麼做並發控制的時候,可以直接蒙個 CAS,很有可能就被你蒙對了。
線程池有多個線程同時沒取到任務,會全部回收嗎
這個題不是很好理解,舉個例子:線程池核心線程數是5,當前工作線程數為6(6>5,意味著當前可以觸發線程回收),如果此時有3個線程同時超時沒有獲取到任務,這3個線程會都被回收銷毀嗎。
也是非常刁鑽的一題,非常細節。但是即使我們記不得源碼的細節了,還是有辦法去推敲出來的。
思路:這道題的核心點在於有多個線程同時超時獲取不到任務。正常情況下,此時會觸發線程回收的流程。
但是我們知道,正常不設置 allowCoreThreadTimeOut 變量時,線程池即使沒有任務處理,也會保持核心線程數的線程。
如果這邊3個線程被全部回收,那此時線程數就變成了3個,不符合核心線程數5個,所以這邊我們可以首先得出答案:不會被全部回收。這個時候面試官肯定會問為什麼?
根據答案不難推測,為了防止本題的這種並發回收問題的出現,線程回收的流程必然會有並發控制。
what?又是並發控制,那不是又可以用到上面剛講的「小技巧」?沒錯,源碼中確實又是使用 CAS 來進行並發控制,從而保證在本例中只會有一個線程成功被回收。
本例相關源碼在:getTask() 方法中。
多個AOP的順序怎麼定
通過 Ordered 和 PriorityOrdered 接口進行排序。
思路:這個問題其實我也也沒準備過,但是我知道在 Spring 中有專門定義了 Ordered 和 PriorityOrdered 接口來實現排序功能,例如:對 BeanFactoryPostProcessor 的排序就是使用的這兩個接口,所以這邊我是直接猜測的用的這兩個接口,結果還真是,所以說平時多積累還是挺重要的。
Object的wait()方法為什麼需要先獲取鎖
這個問題說難也難,說簡單也簡單。
說簡單是因為,大家應該都記得有道題目:「sleep和wait的區別」,答案中有一項就是:「wait會釋放對象鎖,sleep不會」,既然要釋放鎖,那必然要先獲取鎖。
說難是因為如果沒有聯想到這個題目並且沒有了解 wait() 的底層原理,可能就完全沒頭緒了。
在 JVM 源碼中,wait() 的底層邏輯會釋放當前佔有的鎖,在釋放前會進行鎖的檢查,如果當前線程沒有持有鎖,則會直接拋出 IllegalMonitorStateException。
synchronize底層維護了幾個列表存放被阻塞的線程
這題是緊接著上一題的,很明顯面試官想看看我是不是真的對 synchronize 底層原理有所了解。
synchronize 底層使用了3個雙向鍊表來存放被阻塞的線程,3個雙向鍊表分別是:_cxq(Contention queue)、_EntryList(EntryList)、_WaitSet(WaitSet)。
當線程獲取鎖失敗進入阻塞後,首先會被加入到 _cxq 鍊表,_cxq 鍊表的節點會被進一步轉移到 _EntryList 鍊表。
當持有鎖的線程釋放鎖後,_EntryList 鍊表頭結點的線程會被喚醒,該線程稱為 OnDeck 線程,然後該線程會嘗試搶佔鎖。
而當我們調用 wait() 時,線程會被放入 _WaitSet,直到調用了 notify()/notifyAll() 後,線程才被重新放入 _cxq 或 _EntryList,默認放入 _cxq 鍊表頭部。
真題:算法題、手寫代碼
單鍊表反轉、合併兩個有序鍊表、版本號比較
送分題,出這種題說明你前面面的不錯,筆試題只是走個過場,力扣原題:21、165、206。
多線程按順序輸出ABC若干次
思路:考察多線程基礎,Semaphore、Lock + Condition、synchronize 都有解法。
單例模式
手寫單例模式,加上以前幾次的面試,我有印象的至少有兩個大廠問過了,還是挺重要的。通常有五種:懶漢、餓漢、雙重檢測、靜態內部類、枚舉,參考之前的文章:《單例模式詳解》
手寫阻塞隊列
參考 ArrayBlockingQueue,實現 put 和 take 方法即可。
手寫HashMap
JDK 1.8 的版本有點太複雜了,可以寫 JDK 1.7 的版本,使用鍊表 + 數組實現,實現 get 和 put 方法即可。
輸出一個二叉樹的寬度
思路:二叉樹的層序遍歷(力扣102)的簡單變形,記錄下每層的節點個數,取最大值即可。
阿拉伯數字轉漢字
例如:輸入123.123,輸出一百二十三點一二三。
思路:將「零一二三四五六七八九」、「十百千萬億」這些結果用到的字符存到數組,然後將阿拉伯數字轉字符串,遍歷拼接結果。
我當時的思路是將整數部分和小數部分分開處理,小數部分比較容易,主要是整數部分比較難處理,整數部分我當時第一想法是從低位往高位遍歷,然後按每一位拼接,例如123,則拼出:三、二十、一百,最終組成:一百二十三。
這題思路不難想,方法應該有很多種,但是要能通過測試用例還是挺難的,很多細節需要處理,需要反覆調試。
有序列表,找出所有兩數之和為10000的組合
思路:可以用 HashMap 來存遍歷過的數字,每次遍歷時檢查下 HashMap 裡是否有目標數字,時間複雜度O(n),空間複雜度為O(n)。
如果面試官要求空間複雜度更優的解法,則可以用雙指針,從列表的兩邊向中間掃,和小於10000,則左指針右移,和大於10000,則右指針左移,雙指針理論上沒有使用額外的空間。
單鍊表奇數節點移動到最前面
輸入:a1->a2->a3->a4->a5
輸出:a1->a3->a5->a2->a4
思路:也是硬寫,不過這題還是有點規律的,只移動奇數節點,相當於移動一個,跳過一個,移動一個,跳過一個。
字符串的全排列
輸入:s = "abc"
輸出:["abc","acb","bac","bca","cab","cba"]
思路:回溯法,按順序固定每一位,同時排除同一位出現重複的情況,劍指 Offer 38 題。
輸入: [10,9,2,5,3,7,101,18]
輸出: 4 ,最長的上升子序列是 [2,3,7,101],它的長度是 4。
思路:動態規劃,力扣 300 題。
真題:項目、架構、管理
項目介紹
必問的題目,項目介紹主要有兩個重點:
1、要讓面試官知道你在做什麼。可以從項目的背景開始,為什麼要做這個項目,按項目的演進過程、分階段介紹。
2、突出項目的亮點和關鍵技術。面試官畢竟只是在聽一個他沒接觸過的項目,所以他會更容易去抓取候選人介紹中的一些感興趣的「關鍵詞語」來提問,例如:從0到1、技術選型、性能優化、表達式引擎、規則引擎、平臺化、技術重構、架構升級等等。
當然,你如果提到這些詞,就要做好被追問的準備。
技術選型
技術選型主要比較幾個維度:性能、穩定性、接入成本、社區活躍度、業務擴展性、業務特點等等,沒有所謂最好的框架/工具,只有最合適自己的。
這邊拿表達式引擎來作為例子:
我自己選用的是 Aviator,首先我會介紹 Aviator 的基本特點,然後跟 IKExpression、Groovy 進行對比,重點突出 Aviator 的優點:高性能、輕量級,並且強調 Aviator 已經足夠滿足業務需求,同時其作者在阿里方便進行交流等等。
做過最牛逼的功能
我經常看到有同學吐槽這個問題,但是這個問題說實話問的頻率非常高,所以面試前還是得好好準備一下,爭取能讓面試官眼前一亮。此時不裝逼,更待何時。
我這次講的是一個性能優化的案例,將一個接口從性能提升了兩個量級,不看過程只看結果還是有點嚇人的。並且講完案例後,我通常會繼續分享一些自己在性能優化方面的研究和積累。
我們研究和積累的一些方案和技術可能並不適用於自己當前工作的業務場景,但是這些積累可能是很牛逼的,所以這時候你自己要去主動分享出來,讓面試官看到你的亮點。
DDD(領域驅動設計)
這個東西最近挺火的,老闆們也對這個感興趣,因為看起來挺高大上的,並且自己也在簡歷裡面寫了,所以經常被問,印象中應該是這次面試被問的最多的。
DDD不是一套框架,而是一種架構思想,所以每個人都可以有自己的理解和實踐,只要你能講出你的理解,並且講出其優點即可。
可以圍繞設計思想、核心解決的問題、分層架構、優缺點、項目中的應用去介紹。
平臺化
也是老闆們比較感興趣的問題,這個東西就有點涉及到架構方面的知識了。當然,畢竟也5年經驗了,這方面的東西確實得有一些了解了。
首先,介紹平臺化的業務背景,通常是在一個場景下存在不同的業務模式,兩者有區分,但是又有很多共性,所以這個時候去進行平臺改造。
同時強調,在從業務模式從一個增加到兩個的時候,進行平臺化改造是性價比最高的,後續有新的模式就可以直接復用了。
平臺化的核心思想:抽取公用邏輯,根據業務的差異點提供自定義擴展點,不同業務模式通過自定義擴展點實現自己的差異化邏輯,以達到抽象復用,提升新業務模式的開發效率。
項目管理經驗(PM)
5年經驗通常需要在項目或需求中去承擔起一個負責人的角色,所以項目管理也是面試官很喜歡考察的。
總結來說,PM 的相關問題都是圍繞以下來展開:如何保障項目的按時交付,遇到突發問題時怎麼應對。
PM 常見的職責如下:
1)協調各端人員進行需求評審。
2)排期:協調好各端進行排期評估,預留各端人員時間資源,做好總體排期、裡程碑設定。
3)約定項目溝通機制:根據項目判斷是否需要統一項目室開發、各端之間如何同步進展及問題,如遇項目風險,及時向PM反饋,PM可聯合討論,確定最終方案。
4)把控項目進度:及時了解各端的進度,及時了解項目風險(進度緩慢、人員請假等),及時與產品和業務溝通協調,及時作出風險應對。
5)需求變更控制:項目立項後,所有的需求變更,不得在原需求文檔上進行修改。如需修改,需要與PM溝通確認。需求統一交給PM,由PM溝通後郵件通知項目組成員。
6)質量控制:接口提供者,在項目聯調前需要保證主體功能OK;各端在聯調前做好自測;QA測試時保證環境穩定,不要隨意部署。
7)同時作為後端負責人:進行詳細的需求分析和拆解,安排好後端人員的開發,做好codereview、方案設計。
8)項目結束後及時進行總結,觀察項目目標達成情況,及時進行復盤。
穩定性保障
關於穩定性保障的回答思路可以從「事前預防預警」、「事中快速止損」、「事後復盤總結」去展開。
事前預防預警:穩定性架構設計、防禦式編程、業務可降級、力保核心鏈路、慢SQL/接口優化、日常巡檢、灰度發布、業務指標監控、系統指標監控、全鏈路壓測、限流等。
事中快速止損:如果剛上線則快速回滾、及時周知上級及相關業務方、降級開關、限流等,關鍵思路是先止損而不是糾結於問題。
事後復盤總結:找出問題根源、5whys分析法、制定相應的優化措施、組織團隊進行復盤等。
真題:場景題
給出一個場景,問怎麼進行整個系統的設計,包括表結構設計,數據的處理等等。
思路:這種題比較考驗綜合能力,沒法提前準備,只能靠平時的積累了。不過場景題的大方向基本是數據量很大,性能優化這些,所以在這方面有積累的同學可能比較容易對付。
面試技巧
合理安排面試順序
剛開始面試時,一定要先找幾家公司練練手,理想中的公司放在後面,面試是需要時間進入狀態的,所以切記不要一上來就面自己想進的公司。
保持自信
面試時,很多題目其實是自己剛複習過的,所以此時你對這些題目的理解可能是比面試官更好的,所以一定要自信點,自信有利於自己更好的發揮。
主動引導
面試前我們都會準備一些能讓面試官眼前一亮的東西,但是面試時並不一定會被問到,這個時候你得想辦法去引導到這個話題上。
最後
目前國內的 Java 環境競爭挺大的,各種內卷,瘋狂卷,卷的不可開交,要想全部準備好基本不可能。
所以除了做好面試前的準備,平時日常工作也需要多思考、多總結、多積累,在面試遇到沒見過的題目時,才能更從容的應對。
另外,關於算法題,在寫本文的時候,我發現自己已經好多算法開始記憶模糊了,好可怕,感覺自己後面得出點算法題文章,加深下印象,哎,真雞兒難哦。