又到跨年之際,想必在這一年技術成長頗多的猿友們為備戰金三銀四而蠢蠢欲動了吧,工欲善其事必先利其器,停止無病呻吟和眼高手低,腳踏實地地狂刷面試題,offer拿到手軟不再是空談,帝都的雁為大家匯總本人在今次找作業中遇到的面試題,希望可以幫到猿友,
(PS:博主本次找作業參加面試的知名企業有:有快手/位元組/阿里/滴滴/boss直聘/攜程/獵聘/好未來/京東/美團/當當,最終也如愿進入其中一家大廠;面試題基于Java全堆疊,參照個人簡歷的技術堆疊由淺至深詢問,故建議猿友們在簡歷上寫自己hold得住的技能,切莫畫蛇添足,以本人技術堆疊為例,答案均為本人的理解,僅供參考,)
一、設計模式
列舉常被問到的設計模式,
策略(阿里/快手/京東/獵聘)
問:在專案哪些地方使用到了策略設計模式,
答:重構訂單狀態變更邏輯,系統中訂單的狀態有好多個,每一個狀態對應一種業務邏輯,以前的代碼按照if() else if()分支去處理,代碼臃腫且冗余,我定義訂單策略的抽象類抽取共同行為和屬性,再將每個訂單狀態的業務邏輯封裝為訂單策略實作類,并放入spring的IOC中;定義一個列舉,將訂單的狀態和訂單策略實作類的beanID進行映射,最后定義策略背景關系物件,用于互動即可,
單例(快手/boss直聘/滴滴)
問:手寫一個單例,
答:我一般為了簡單,會直接寫餓漢式,但會向面試官闡述單例的一些實作方式和注意事項,
單例是指一個類在一個JVM中僅有一個實體物件,常見的實作方式有餓漢式、懶漢式、執行緒安全的懶漢式、雙重檢驗鎖+volatile、靜態內部類、列舉等方式,反射和反序列化可以破壞單例,所以需要在單例的構造中再次判斷實體物件是否已創建,進而拋出例外進行規避,
代理(獵聘/位元組/阿里)
問:動態代理的實作方式,
答:繼承目標類或實作目標類介面,
JDK動態代理基于實作目標類介面,寫一個方法增強器實作invocationHandler介面,重寫其invoke方法,然后通過Proxy.newInstance的方式傳入目標類的類加載器、目標類實作的介面,以及自定義的方法增強器,然后創建代理物件,本質上是動態拼接了一個實作了目標類介面的代理類的字串,將這個字串輸出至本地的一個Java檔案中,再通過Java編譯器編譯,變為class檔案,類加載器將其加載至JVM記憶體,以反射的方式進行實體化使用,
CGLIB動態代理基于繼承目標類,寫一個MethodIntercepter的實作類,重寫其invokeSuper方法,然后通過Enhancer的方式進行創建使用,底層通過ASM位元組碼技術生成了三個檔案:代理類位元組碼、目標類索引檔案、代理類索引檔案,索引檔案中將當前類中方法名稱和引數串列型別組成簽名,按照hash生成一個下標,然后通過switch case的方式列舉,故而訪問起來會比反射的方式更快,
觀察者(boss直聘/攜程/滴滴/美團/當當)
問:觀察者的使用場景,
答:訂單發貨后需要給商家發短信和郵件,由于我們系統前期沒有引入MQ,所以采用異步執行緒去進行解耦,發貨可以看作一個事件,短信和郵件可以看作兩個觀察者,采用spring的Event事件通知去實作此功能,
模板方法(阿里/快手/京東)
問:專案中怎么使用的模板方法,
答:系統中很多批量匯入的業務代碼冗余,且其大致操作流程相似,為了便于維護,故而將批量匯入的流程抽取至抽象類中,具體的行為抽象交給不同的業務代碼實作即可,
題外話(京東/獵聘/好未來/滴滴/當當)
問:目前對于設計模式的使用分為截然不同的兩個派系,大力擁護和強烈反對,如何看待設計模式?
答:設計模式有時可能不直觀,非本人撰寫的代碼,閱讀起來有可能有些繞,不利于新手修改,但它方便擴展,且可以使業務代碼之間很好的解耦,對于原始碼的閱讀也有很大幫助,

二、JAVA基礎
Java基礎包括JDK和spring體系、mybatis,
執行緒池(快手/位元組/阿里/滴滴/boss直聘/攜程/獵聘/好未來/京東/美團/當當)
問:執行緒池的核心組件有哪些?為什么不推薦使用JDK自帶執行緒池?如何手寫一個執行緒池,
答:執行緒池核心引數有核心執行緒數(執行緒池啟動后,一直處于活躍狀態的執行緒)、佇列長度(核心執行緒都在處理任務時,新來的任務會放入此佇列中)、最大執行緒數(佇列滿了后,會再開啟一定數量的執行緒,即執行緒池最多創建的執行緒數量)、拒絕策略(佇列滿了,最大執行緒數的執行緒均在處理任務,此時還有任務投遞,則進行的動作),
JDK自帶的執行緒池有四種:固定長度(佇列無界)、可快取(最大執行緒數無界)、可定時(最大執行緒數無界)、單例(佇列無界),由于其無界(佇列或最大執行緒數),可能會造成執行緒太多或任務太多帶來的OOM(out of memory)問題,我研究過定時執行緒池,內部維護了一個延時作業佇列(底層采用小堆頂的方式實作),任務攜帶時間間隔的引數,每當任務要執行時,會把當前系統時間+時間間隔后,把這個任務再次投遞至延時作業佇列中,
手寫執行緒池思路:定義阻塞佇列,創建核心執行緒數的執行緒,使其一直處于活躍狀態(死回圈即可),當有任務需要執行時,從佇列中獲取任務執行,
并發包(快手/位元組/阿里/滴滴/boss直聘/攜程/獵聘/好未來/京東/美團/當當)
問:sync和lock的區別,公平鎖與非公平鎖的區別,CAS有什么問題?如何解決?
答:我個人認為lock就是仿照sync的底層原理以Java代碼的方式實作了一次,
Sync內部維護了一個monitor物件,用于管理鎖的開銷,其內部有鎖池(搶鎖失敗的執行緒)、等待池(呼叫鎖wait方法的執行緒)、鎖持有執行緒、重入次數、競爭邏輯,
Sync存在鎖的膨脹(粗化),偏向鎖:即一把鎖在一段時間內只被同一個執行緒持有,那么當這個執行緒來訪問鎖時,不會對其進行加鎖操作,而是在鎖物件的物件頭markword中存放偏向鎖的資訊,記錄當前執行緒ID,用于比較,輕量級鎖:一個執行緒持有一把鎖的時間非常短,那么搶鎖失敗的執行緒不會直接阻塞,而是自旋等待(將鎖物件頭中偏向鎖資訊拷貝至自己執行緒的作業記憶體中,以CAS的方式自旋),重量級鎖:如果多次自旋失敗,則不會繼續這種消耗CPU資源的行為,而是直接將其阻塞,等待鎖的釋放,
Lock鎖本質上是AQS的一層封裝,其記憶體也有狀態(記錄鎖重入次數)、阻塞雙向鏈表(搶鎖失敗的執行緒)、鎖持有執行緒、condition的單向等待鏈表(呼叫condtion.await方法的執行緒),
AQS默認創建為非公平鎖,當執行緒獲取鎖失敗時,會短暫自旋一次,然后將當前執行緒通過LockSupport的方式進行阻塞,然后封裝為NODE節點,放入阻塞鏈表的末尾,當鎖釋放后,從阻塞鏈表的頭部取出一個節點,將執行緒喚醒,繼續搶奪鎖資源,由于期間可能出現非阻塞鏈表的執行緒參與鎖資源的爭奪,對排隊的執行緒不公平,所以為非公平鎖,
公平鎖在非公平鎖的基礎上進行判斷,搶到鎖的執行緒如果不是阻塞鏈表頭部的執行緒,則將其阻塞,放入阻塞鏈表末尾排隊等待,
CAS自旋消耗CPU資源,且存在ABA問題,可以通過版本號去解決,
參考:Synchronized的花花腸子和AQS的傀儡之Lock鎖
集合原始碼(快手/位元組/阿里/boss直聘/攜程/獵聘/京東)
問:arrayList底層實作,HashMap1.7和1.8有什么區別,ConcurrentHashMap1.7和1.8有什么區別,
答:arrayList底層采用陣列實作,默認長度10,1.5倍擴容,擴容需要陣列的拷貝,洗掉需要移位,由于共享全域的陣列,且其對陣列的維護沒有加鎖,故非執行緒安全,存在fail-fast機制,
HashMap1.7采用陣列+單向鏈表實作,初始容量16,負載因子0.75,允許存放key為空的資料(放在下標為0處),將key的hashcode值通過hash演算法得出hash值,再將hash值與陣列的長度按位與,得出下標,在下標處以頭插法存放資料,由于沒有對共享的陣列加鎖,非執行緒安全,2倍擴容,高并發下,由于頭插法和非執行緒安全,可能導致鏈表死回圈,HashTable是在HashMap的基礎上,對其所有方法加sync保證執行緒安全,但HashTable不允許出現空值,
HashMap1.8采用陣列+單向鏈表+紅黑樹實作,當單條鏈表的長度大于8且集合元素超過64,會將這條鏈表轉為紅黑樹,提升查詢效率(鏈表時間復雜度為O(n),紅黑樹時間復雜度為O(log(n)),鏈表的插入方式改為尾插法,
ConcurrentHashMap1.7底層采用16個segment物件,segment和hashTable類似,所以ConcurrentHashMap1.7保證執行緒安全的方式采用分段鎖,從而提升效率,
ConcurrentHashMap1.8是在HashMap1.8基礎上優化,對其所有非執行緒安全的操作加鎖處理,比如陣列初始化和擴容,采用CAS方式保證執行緒安全,對于單個下標下資料的存放,采用sync鎖起來,粒度更細,且當某個執行緒訪問ConcurrentHashMap時,如果發現正在擴容,不會阻塞這個執行緒,而是幫助ConcurrentHashMap完成擴容,
Java記憶體結構(快手/阿里/獵聘/好未來/京東/當當)
問:類加載程序,雙親委派如何打破,Java記憶體如何劃分,常用的垃圾回收器和垃圾回收演算法有哪些,
答:類加載器通過編譯、鏈接(驗證、準備、決議)、初始化的操作將class檔案加載至Java記憶體中,
雙親委派是指Java查找類的方式自上而下(啟動類加載器、擴展類加載器、應用類加載器、自定義類加載器),類加載器中有個findClass方法,遞回向上查找上級類加載器,我們只需要繞過這個方法即可,
Java記憶體機構分為:
執行緒獨占:堆疊(由堆疊幀組成,每一個堆疊幀就是一個方法,堆疊幀由區域變數表、運算元堆疊、動態鏈接、回傳地址組成)、程式計數器(記錄當前執行緒運行的位置)和本地方法堆疊(被native修飾,與C通訊),
執行緒共享:元空間(存放靜態資訊,類的位元組碼資訊)、堆(創建的物件、字串常量池),
堆記憶體:以分代演算法劃分為新生代和老年代,新生代又分為eden區(剛創建的物件)、from和to區(復制演算法,用于年齡累加),
常用垃圾回收演算法有:標記清除、標記整理、復制、GCROOT,
常用垃圾回收器有:CMS、G1、Servier New/Servier Old
參考:被解刨的JVM
Java記憶體模型和volatile(快手/阿里/滴滴/攜程/獵聘/京東/當當)
問:介紹下Java記憶體模型,Volatile如何保證記憶體可見的,
答:Java記憶體模型分為主記憶體和作業記憶體(本地記憶體),主記憶體存放全域共享變數資料,而作業記憶體存放這些共享變數的副本資料,每一個執行緒都會開辟自己的作業記憶體,而Volatile修飾的變數通過MESI快取一致性協議去保證一致性,即當變數修改,CPU總線嗅探機制會捕獲到它的變動,將其內容重繪至主記憶體,然后將其它作業記憶體的變數值置為無效,使得其它作業記憶體變數的值重新從主記憶體獲取此變數值,保證一致,
參考:volatile與JMM的那些恩怨情仇
mybatis組件(快手/阿里)
問:介紹下Mybatis的原理,
答:詳情參考通俗易懂的Mybatis作業原理
spring組件(快手/位元組/阿里/獵聘/好未來/京東/當當)
問:介紹下bean的生命周期,AOP如何實作的,如何解決回圈依賴問題,宣告式事務嵌套會發生什么問題,
答:spring容器啟動時,會初始化spring的各種組件:beanFactory、后置處理器、event與listener的系結、初始化單例物件等,初始化單例物件時,反射其無參構造創建物件,然后進行屬性賦值,檢查aware的依賴資訊,執行后置處理器的前置操作,執行自定義init方法,執行后置處理器后置處理,
Spring通過三級快取來解決單例的回圈依賴,多例需要我們通過@primy或@qualify手動宣告,
AOP也是后置處理器的一種特殊實作,在后置處理中判斷當前類是否實作介面,進而判斷使用JDK動態代理還是CGLIB動態代理,在對應的invoke中,通過責任鏈+遞回的方式去依次執行切面的通知,最后執行目標方法,
事務是一種特殊的通知,通過手動try catch來提交或回滾事務,
當同類的事務嵌套時,this會使其失效,不同類事務嵌套,按照事務傳播機制進行判斷是否回滾,
springMVC組件(快手/阿里)
問:攔截器的原理,
答:springMVC的核心類DispatherServlet的doDispath方法中為springMVC的執行原理,通過模版方法獲取所有的攔截器,回圈呼叫執行其三個抽象方法的實作,

三、主流技術
以簡歷為例,
Redis(快手/位元組/阿里/滴滴/boss直聘/攜程/獵聘/好未來/京東/美團/當當)
問:redis的資料結構,淘汰策略,持久化機制,集群方式,分布式鎖,分片原理,快取擊穿/穿透/雪崩的原因和解決方案,
答:參考程式猿必備的Redis常見功能知識點,這些你都會嗎?
Zookeeper(滴滴/京東/當當)
問:ZK節點的型別,分布式鎖原理,集群原理,Base和CAP的理論,
答:節點型別分為有序臨時、無序臨時、有序持久和無序持久,支持權限認證,有著強大的事件通知功能,
分布式鎖分為臨時節點和有序臨時節點,前者存在羊群效應,后者類似于Java的lock公平鎖,
集群要保證過半機制,通過MVCC和myid來選取事務ID最大、性能最好的節點當選leader,其資料同步遵循最終一致性(BASE),保證CAP中的CP,通過過半機制保證腦裂現象,
訊息佇列(快手/阿里/滴滴/獵聘/好未來/京東/美團/當當)
問:MQ的作業方式有哪些,如何確保訊息不丟失,如何解決重復消費,如何解決順序消費,基于mq解決分布式事務,
答:作業方式有點對點、能者多勞(消費者手動ACK)、發布訂閱fanout、路由direct、模糊路由topic,
訊息不丟失:生產者投遞訊息成功后,采用confirm確保投遞成功;消費者消費訊息后,手動ACK保證消費成功;mq通過持久化機制將訊息持久化至磁盤,
重復消費:記錄訊息ID(雪花演算法生成),消費訊息前主動查詢比對,
順序消費:多個訊息通過key保證其落在同一臺broker節點,然后使其被一個消費者消費,消費者在自己本地可采用記憶體佇列的方式提升消費效率,
分布式事務:阿里的rocketMQ支持事務訊息,通過兩端提交協議去保證事務參與者的事務提交或回滾的一致性,
微服務(快手/位元組/阿里/滴滴/攜程/好未來/京東/美團)
問:什么是服務治理,服務注冊和發現的程序,網關的作業原理,如何做到本地負載均衡,服務熔斷器的原理,
答:服務治理就是管理服務之間呼叫混亂的現象,注冊中心統一管理服務地址,其他服務啟動時將自身的資訊以服務名稱的鍵值對投遞注冊至注冊中心,若想要獲取其它服務地址,也是通過其它服務名稱去注冊中心查找到集群地址,然后本地通過負載均衡演算法進行輪詢,
熔斷器可以實作服務降級、熔斷和執行緒隔離等功能,當此介面被熔斷器保護時,只要請求時長超過預設時間,就會走指定的方法進行回應,實作降級處理,熔斷是指如果請求此介面的執行緒數太多,則會走服務降級處理,我們也可以對熱點介面實作執行緒池隔離的方式提升性能,不同的介面采用不同的執行緒池處理,
Netty(位元組/阿里/boss直聘/攜程/獵聘/好未來/京東/美團)
問:netty的作用,什么是NIO多路復用,為什么NIO在LINUX比WINDOWS性能要好,
答:netty本質上就是對NIO的多路復用做了一層包裝,IO模型中分為BIO(獲取不到就阻塞),NIO(獲取不到不阻塞)和AIO(異步),為了提升性能和降低CPU使用,采用一個執行緒統一管理這些socket的IO,Windows中才selector去不停的回圈這些IO,但IO不一定每次回圈都有資料,故會出現空輪詢的情況;而linux采用epoll的事件驅動回呼,當socket的IO有資料時主動通知機制去獲取資料,故效率更高,

四、資料庫
主要是mysql,
資料結構(快手/位元組/阿里/滴滴/boss直聘/攜程/獵聘/好未來/京東/美團)
問:MySQL的索引使用什么資料結構,為什么使用B+樹,
答:mysql索引支持B樹、B+樹、Hash,默認采用B+樹,hash查詢快,但對范圍查詢不友好,B樹的非葉子節點存放了索引值和資料(或資料地址),導致其單個節點存放的索引值變少,樹的高度提升,增加IO次數,B+樹非葉子節點只存放索引值,故而樹的高度小,IO次數少,且葉子節點是一條有序的鏈表,對范圍查詢友好,
參考:mysql定位和優化慢查詢的方案
底層原理(滴滴/攜程)
問:mysql的redolog、undolog和binlog分別有什么作用,
答:mysql默認采用innodb作為存盤引擎,innodb維護了一個buffer pool的緩沖池來快取資料,當更新資料時,會先將資料通過隨機IO的方式從磁盤讀取至緩沖池,在更新緩沖池資料前,會將緩沖池的資料寫入至undolog中,用于事務回滾;更新完緩沖池資料后,將緩沖池的資料寫入redolog中,用于災備的恢復;然后將緩沖池的資料同步至物理磁盤,并記錄在binlog日志,用于做主從復制或與mq同步,
參考:mysql查詢和修改的底層原理
索引(快手/位元組/阿里/滴滴/獵聘/好未來/京東/美團/當當)
問:為什么索引可以提高查詢效率,聚簇索引和非聚簇索引的區別,聯合索引的使用方式,
答:資料是按照索引的方式有序存放,如同我們的新華字典存放漢字一樣,聚餐索引即主鍵索引,資料和索引存放在一個檔案中,而非聚簇索引的葉子節點存放的是主鍵的ID,聯合索引遵循最左原則,
參考:mysql定位和優化慢查詢的方案
事務隔離級別(快手/阿里)
問:事務隔離級別有哪些,
答:讀未提交(一個事務讀到另一個事務未提交的資料,有臟讀現象)、讀已提交(一個事務讀到另一個事務已經提交的資料,有不可重復度現象)、可重復讀(事務之間通過MVCC隔離,但有幻讀現象)、串行化(單執行緒),
參考:mysql事務隔離級別以及MVCC的底層原理
sql優化(快手/位元組/阿里/滴滴/獵聘/好未來/京東/美團/當當)
問:專案中如何優化sql,以什么為標準,需要注意什么,
答:以阿里開發手冊為準,通過explain去輸出sql的執行計劃,將其type優化為range級別以上(至少為range),同時避免filesort的記憶體排序情況,
參考:mysql定位和優化慢查詢的方案
歡迎大家和帝都的雁積極互動,頭腦交流會比個人埋頭苦學更有效!共勉!
公眾號:帝都的雁

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/243298.html
標籤:其他

