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