阿里Java一面,难度适中!(下篇)
上一次因為文章篇幅和個人精力有限的原因,只分享了淘天的前 6 道題及其答案(點擊訪問上一篇)。接下來,咱們把其他幾道題面試題及答案也分享給大家。
1.公司簡介
淘天集團就是“淘寶”+“天貓”的結合,其集團擁有淘寶、天貓、1688、閑魚等商業品牌,并通過天貓國際、淘寶直播、天貓超市、淘寶買菜、阿里媽媽等業務,提供進口、直播、超市、買菜、數字營銷等服務。
2.面試背景
面試崗位:Java 開發工程師
面試時間:2023.10.28
3.面試問題
- 為什么要用 Redis?有預估 QPS 的提升幅度嗎?
- Redis 內存不夠用怎么辦?
- 是否定義、設計過業務模型?
- 百萬級用戶規模服務上線的話需要做什么?
- JVM 怎么創建一個對象?
- 有哪些場景會觸發類的加載?
- 如果不使用雙親委派會有什么問題?
- 一個線程包含哪些線程狀態?
- 線程池執行任務的過程?
- 線程同步有哪些策略和類?有沒有實測過關鍵字的性能?
- SpringBoot 搭建的 Web 服務處理過程?
- 有沒有看過開源框架的源碼,舉一個例子講講?
PS:前 6 道問題上篇已經解答過,這里就不再贅述了,大家可以自行查看:https://mp.weixin.qq.com/s/VdB8_ZUCEm4pVOblrcMS-w
4.答案解析
如果不使用雙親委派會有什么問題?
答:雙親委派模型指的是,當一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最 終都應該傳送到最頂層的啟動類加載器中,只有當父加載器反饋自己無 法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去完成加載。
自 JDK 1.2 以來,Java 一直保持著三層類加載器、雙親委派的類加載架構器,如下圖所示:
其中:
- 啟動類加載器:加載 JDK 中 lib 目錄中 Java 的核心類庫,即$JAVA_HOME/lib目錄。 擴展類加載器。加載 lib/ext 目錄下的類;
- 應用程序類加載器:加載我們寫的應用程序;
- 自定義類加載器:根據自己的需求定制類加載器。
如果不使用雙親委派模型,可能存在以下問題:
- 安全性問題:雙親委派模型可以通過從上層類加載器向下層加載器委派類加載請求來提高安全性。如果沒有雙親委派機制,那些由上層類加載器加載的核心類可能會被替換成惡意代碼,從而導致安全漏洞。
- 資源浪費問題:沒有雙親委派機制,每個類加載器都有自己的類加載搜索路徑和加載規則。這可能導致同一個類被不同的類加載器重復加載,造成資源的浪費。
- 類沖突問題:在沒有雙親委派機制的情況下,不同的類加載器可以獨立加載相同的類。這可能導致類的沖突和不一致性,因為同一個類在不同的類加載器中會有多個版本存在,最終導致類的不一致問題。
雙親委派模型是保證 Java 應用程序的穩定性和安全性的重要機制,使用雙親委派模型能夠避免類的沖突、提高安全性、節省資源,并保證類的一致性。
線程中包含哪些狀態?
答:在 Java 中,線程狀態總共有以下 6 種:
- NEW(初始化狀態):線程剛被創建時是初始狀態,線程對象被創建,但還未調用 start() 方法啟動線程。
- RUNNABLE(可運行狀態):線程正在 Java 虛擬機中執行,調用 start() 方法后,線程開始執行,變為此狀態。
- BLOCKED(阻塞狀態):線程被阻塞,等待獲取鎖資源。當線程執行 synchronized 關鍵字標識的代碼塊時,如果無法獲取到鎖資源,則線程進入阻塞狀態。當其他線程釋放鎖資源后,該阻塞線程進入就緒狀態,等待競爭鎖資源。
- WAITING(無時限等待狀態):線程通過調用 Object.wait() 方法進入等待狀態,直到被其他線程通過 Object.notify() 或 Object.notifyAll() 來喚醒。
- TIMED_WAITING(有時限等待狀態):線程通過調用 Thread.sleep(long millis) 方法或 Object.wait(long timeout) 方法進入計時等待狀態。在指定的時間段內,線程會一直保持計時等待狀態,直到到達指定時間或被其他線程喚醒。
- TERMINATED(終止狀態):線程執行完成或者異常終止,即線程生命周期結束,線程進入終止狀態后不可再次轉換為其他狀態。
線程狀態的轉換流程如下圖所示:
線程池執行任務的過程?
答:線程池的執行流程如下(當任務來了之后):
- 先判斷當前線程數是否大于核心線程數?如果結果為 false,則新建線程并執行任務;
- 如果結果為 true,則判斷任務隊列是否已滿?如果結果為 false,則把任務添加到任務隊列中等待線程執行;
- 如果結果為 true,則判斷當前線程數量是否超過最大線程數?如果結果為 false,則新建線程執行此任務;
- 如果結果為 true,則將執行線程池的拒絕策略。
執行流程如下圖所示:
線程同步有哪些策略和類?有沒有實測過關鍵字的性能?
答:線程同步是為了保證多線程環境下數據的一致性和協調線程之間的執行順序。
在 Java 中,有多種線程同步的策略和類有以下這些:
- synchronized 關鍵字:通過在代碼塊或方法上加上 synchronized 關鍵字,可以實現對代碼塊或方法的同步訪問。當一個線程獲取到了對象的鎖資源,其他線程就無法進入該代碼塊或方法,只能等待鎖資源的釋放。
- ReentrantLock 類:它是顯示鎖的一種實現,提供了可重入的鎖機制,與 synchronized 關鍵字相比,ReentrantLock 提供了更高的靈活性和額外的功能,例如設置等待時間、中斷等待、公平性等。
- Condition 類:與 ReentrantLock 類一起使用,通過創建多個 Condition 對象,可以實現更加精細化的線程等待和喚醒機制。
- Semaphore 類:通過設置信號量的數量,可以控制同時訪問某個資源的線程數量。
- CountDownLatch 類:通過設置計數器的值,可以控制某個任務等待其他一組任務完成后再執行。
- CyclicBarrier 類:通過設置參與線程數量,當所有線程都達到柵欄點后,所有線程會被釋放,并繼續執行。
然而,這些線程同步類的性能是和具體使用場景有關的,不同的業務場景其性能是不同的,synchronized 在早期的版本(JDK 1.6 之前)使用的是重量級鎖,所以性能不是很好。但在 JDK 1.6 時經過了鎖升級的優化(無鎖、偏向鎖、輕量級鎖、重量級鎖),因此絕大部分場景使用更易操作的 synchronized 就足夠了,但如果需要創建公平鎖或有多個任務需要協調一起執行時可以考慮其他的同步關鍵字。
Semaphore 類介紹和使用可以看我之前寫的文章:https://juejin.cn/post/6953417207191699464
CountDownLatch 類介紹和使用可以看我之前寫的這篇文章:https://juejin.cn/post/6945655452952854536
CyclicBarrier 類介紹和使用可以看我之前寫的這篇文章:https://www.yuque.com/vipstone/tp0yhd/owd178
SpringBoot 搭建的 Web 服務處理過程?
答:Spring Boot 內部使用 Servlet 容器(如 Tomcat、Jetty 等)來處理 Web(HTTP)請求和響應。
它的執行流程可以分為以下幾個關鍵步驟:
- 客戶端發起請求:客戶端通過 HTTP 協議向 Spring Boot 應用程序發送請求。請求可以包括 HTTP 方法(GET、POST等)、URL 路徑、請求頭、請求參數等信息。
- 路由匹配:Spring Boot 應用程序根據請求的 URL 路徑,通過路由匹配將請求分發到對應的處理器。
- 處理器處理請求:匹配到的處理器(Controller)會執行相應的方法來處理請求。在 Spring Boot 中,Controller 會被注解標識,Spring Boot 會根據注解配置自動將請求分發給對應的 Controller。Controller 方法可以接收請求參數、處理業務邏輯,并返回響應結果。
- 調用服務層:Controller 可以調用業務邏輯處理層(Service)來進行具體的業務處理。Service 層通常負責處理復雜的業務邏輯,如數據庫讀寫、事務管理等。
- 返回響應結果:處理器處理完請求后,將處理結果封裝成 HTTP 響應返回給客戶端。響應可以包括 HTTP 狀態碼、響應頭、響應體等信息。
- 客戶端接收響應:客戶端收到響應后,根據響應的內容進行相應的處理,如解析 JSON 數據、渲染頁面等。
- 結束請求生命周期:請求處理完成后,會結束請求的生命周期,釋放相關資源。
有沒有看過開源框架的源碼,舉一個例子講講?
答:這個問題沒有固定的答案了,個人可根據自己的情況來說,這個給大家提供兩個比較典型的案例。
Spring Boot 請求執行源碼
你可以說你看過 Spring Boot 的源碼,其中記憶比較深刻的就是請求進入 Spring Boot 中的執行流程,他的執行流程是這樣的,所有請求先進入 DispatcherServlet(前端控制器),調用其父類 FrameworkServlet service 方法,核心源碼如下:
/**
* Override the parent class implementation in order to intercept PATCH requests.
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
} else {
super.service(request, response);
}
}
繼續往下看,processRequest 實現源碼如下:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 省略一堆初始化配置
try {
// 真正執行邏輯的方法
doService(request, response);
}
catch (ServletException | IOException ex) {
...
}
}
doService 實現源碼如下:
protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception;
doService 是抽象方法,由 DispatcherServlet 重寫實現了,源碼如下:
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 省略初始化過程...
try {
doDispatch(request, response);
}
finally {
// 省略其他...
}
}
此時就進入到了 DispatcherServlet 中的 doDispatch 源碼了:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 獲取原生請求
HttpServletRequest processedRequest = request;
// 獲取Handler執行鏈
HandlerExecutionChain mappedHandler = null;
// 是否為文件上傳請求, 默認為false
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 檢查是否為文件上傳請求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 獲取能處理此請求的Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 獲取適配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 執行攔截器(鏈)的前置處理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 真正的執行對應方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
// 忽略其他...
}
通過上述的源碼我們可以看到,請求的核心代碼都在 doDispatch 中,他里面包含的主要執行流程有以下這些:
- 調用 HandlerExecutionChain 獲取處理器:DispatcherServlet 首先調用 getHandler 方法,通過 HandlerMapping 獲取請求對應的 HandlerExecutionChain 對象,包含了處理器方法和攔截器列表。
- 調用 HandlerAdapter 執行處理器方法:DispatcherServlet 使用 HandlerAdapter 來執行處理器方法。根據 HandlerExecutionChain 中的處理器方法類型不同,選擇對應的 HandlerAdapter 進行處理。常用的適配器有 RequestMappingHandlerAdapter 和 HttpRequestHandlerAdapter。
- 解析請求參數:DispatcherServlet 調用 HandlerAdapter 的 handle 方法,解析請求參數,并將解析后的參數傳遞給處理器方法執行。
- 調用處理器方法:DispatcherServlet 通過反射機制調用處理器方法,執行業務邏輯。
- 處理攔截器:在調用處理器方法前后,DispatcherServlet 會調用攔截器的 preHandle 和 postHandle方法進行相應的處理。
- 渲染視圖:處理器方法執行完成后,DispatcherServlet 會通過 ViewResolver 解析視圖名稱,找到對應的 View 對象,并將模型數據傳遞給 View 進行渲染。
- 生成響應:View 會將渲染后的視圖內容生成響應數據。
Spring Cloud LoadBalancer 負載均衡源碼
當然,除了 Spring Boot 外,你還可以講一下 Spring cloud 微服務的源碼,比如業務代碼比較簡單的 Spring Cloud LoadBalancer 的源碼,這樣既能展現自己會微服務,而且掌握的還不錯。因為微服務在企業中應用廣泛,所以熟練掌握微服務是一個很大的加分項。
Spring Cloud LoadBalancer 中內置了兩種負載均衡策略:
- 輪詢負載均衡策略
- 隨機負載均衡策略
輪詢負載均衡策略的核心實現源碼如下:
// ++i 去負數,得到一個正數值
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
// 正數值和服務實例個數取余 -> 實現輪詢
ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size());
// 將實例返回給調用者
return new DefaultResponse(instance);
隨機負載均衡策略的核心實現源碼如下:
// 通過 ThreadLocalRandom 獲取一個隨機數,最大值為服務實例的個數
int index = ThreadLocalRandom.current().nextInt(instances.size());
// 得到實例
ServiceInstance instance = (ServiceInstance)instances.get(index);
// 返回
return new DefaultResponse(instance);
小結
淘天集團一個標準的大廠,其薪資是比較高的,校招也能給到 30W 以上,社招薪資也不會太低,但其實看了他的面試題也可以發現,他的面試題其實不難,所以好好準備面試,也是有很大的幾率進大廠的哦。
本文已收錄到我的面試小站 www.javacn.site,其中包含的內容有:Redis、JVM、并發、并發、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、設計模式、消息隊列等模塊。
總結
以上是生活随笔為你收集整理的阿里Java一面,难度适中!(下篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《流畅的Python》 读书笔记 第7章
- 下一篇: 【scipy 基础】--线性代数