美團社招一面,比預(yù)想的簡單
面試這件事就很玄學(xué),有時候你覺得他可能很難,但面完之后竟然出奇的順利,問的問題你都會;有些你覺得這次面試應(yīng)該很簡單,但去了之后就被問懵了,所以面試這件事有很多一部分運氣的成分。
所以說,在沒有 Offer 之前就是多準(zhǔn)備、楞慫面,主打一個大力出奇跡。
這不,逛牛某時,看到這套題就很氣,感慨這位老兄命怎么這么好?
1.線程池有幾種實現(xiàn)方式?
線程池的創(chuàng)建方法總共有 7 種,但總體來說可分為 2 類:
- 通過 ThreadPoolExecutor 創(chuàng)建的線程池。
- 通過 Executors 創(chuàng)建的線程池。
線程池的創(chuàng)建方式總共包含以下 7 種(其中 6 種是通過 Executors 創(chuàng)建的,1 種是通過 ThreadPoolExecutor 創(chuàng)建的):
- Executors.newFixedThreadPool:創(chuàng)建一個固定大小的線程池,可控制并發(fā)的線程數(shù),超出的線程會在隊列中等待。
- Executors.newCachedThreadPool:創(chuàng)建一個可緩存的線程池,若線程數(shù)超過處理所需,緩存一段時間后會回收,若線程數(shù)不夠,則新建線程。
- Executors.newSingleThreadExecutor:創(chuàng)建單個線程數(shù)的線程池,它可以保證先進先出的執(zhí)行順序。
- Executors.newScheduledThreadPool:創(chuàng)建一個可以執(zhí)行延遲任務(wù)的線程池。
- Executors.newSingleThreadScheduledExecutor:創(chuàng)建一個單線程的可以執(zhí)行延遲任務(wù)的線程池。
- Executors.newWorkStealingPool:創(chuàng)建一個搶占式執(zhí)行的線程池(任務(wù)執(zhí)行順序不確定)【JDK 1.8 添加】。
- ThreadPoolExecutor:最原始的創(chuàng)建線程池的方式,它包含了 7 個參數(shù)可供設(shè)置,會更加可控。
2.線程池的參數(shù)含義?
問到線程池參數(shù)的含義,一定是問 ThreadPoolExecutor 參數(shù)的含義,這七個參數(shù)的含義分別是:7 個參數(shù)代表的含義如下:
參數(shù) 1:corePoolSize
核心線程數(shù),線程池中始終存活的線程數(shù)。
參數(shù) 2:maximumPoolSize
最大線程數(shù),線程池中允許的最大線程數(shù),當(dāng)線程池的任務(wù)隊列滿了之后可以創(chuàng)建的最大線程數(shù)。
參數(shù) 3:keepAliveTime
最大線程數(shù)可以存活的時間,當(dāng)線程中沒有任務(wù)執(zhí)行時,最大線程就會銷毀一部分,最終保持核心線程數(shù)量的線程。
參數(shù) 4:unit:
單位是和參數(shù) 3 存活時間配合使用的,合在一起用于設(shè)定線程的存活時間 ,參數(shù) keepAliveTime 的時間單位有以下 7 種可選:
- TimeUnit.DAYS:天
- TimeUnit.HOURS:小時
- TimeUnit.MINUTES:分
- TimeUnit.SECONDS:秒
- TimeUnit.MILLISECONDS:毫秒
- TimeUnit.MICROSECONDS:微妙
- TimeUnit.NANOSECONDS:納秒
參數(shù) 5:workQueue
一個阻塞隊列,用來存儲線程池等待執(zhí)行的任務(wù),均為線程安全,它包含以下 7 種類型:
- ArrayBlockingQueue:一個由數(shù)組結(jié)構(gòu)組成的有界阻塞隊列;
- LinkedBlockingQueue:一個由鏈表結(jié)構(gòu)組成的有界阻塞隊列;
- SynchronousQueue:一個不存儲元素的阻塞隊列,即直接提交給線程不保持它們;
- PriorityBlockingQueue:一個支持優(yōu)先級排序的無界阻塞隊列;
- DelayQueue:一個使用優(yōu)先級隊列實現(xiàn)的無界阻塞隊列,只有在延遲期滿時才能從中提取元素;
- LinkedTransferQueue:一個由鏈表結(jié)構(gòu)組成的無界阻塞隊列。與SynchronousQueue類似,還含有非阻塞方法;
- LinkedBlockingDeque:一個由鏈表結(jié)構(gòu)組成的雙向阻塞隊列。
較常用的是 LinkedBlockingQueue 和 Synchronous,線程池的排隊策略與 BlockingQueue 有關(guān)。
參數(shù) 6:threadFactory
線程工廠,主要用來創(chuàng)建線程,默認為正常優(yōu)先級、非守護線程。
參數(shù) 7:handler
拒絕策略,拒絕處理任務(wù)時的策略,系統(tǒng)提供了 4 種可選:
- AbortPolicy:拒絕并拋出異常。
- CallerRunsPolicy:使用當(dāng)前調(diào)用的線程來執(zhí)行此任務(wù)。
- DiscardOldestPolicy:拋棄隊列頭部(最舊)的一個任務(wù),并執(zhí)行當(dāng)前任務(wù)。
- DiscardPolicy:忽略并拋棄當(dāng)前任務(wù)。
默認策略為 AbortPolicy。
3.鎖升級的過程?
鎖升級的過程指的是 synchronized 鎖升級的過程,synchronized 鎖升級機制也叫做鎖膨脹機制,此機制誕生于 JDK 6 中。
在 Java 6 及之前的版本中,synchronized 的實現(xiàn)主要依賴于操作系統(tǒng)的 mutex 鎖(重量級鎖),而在 Java 6 及之后的版本中,Java 對 synchronized 進行了升級,引入了鎖升級的機制,可以更加高效地利用 CPU 的多級緩存,提升了多線程并發(fā)性能。
synchronized 鎖升級的過程可以分為以下四個階段:無鎖狀態(tài)、偏向鎖、輕量級鎖和重量級鎖。其中,無鎖狀態(tài)和偏向鎖狀態(tài)都屬于樂觀鎖,不需要進行鎖升級,鎖競爭較少,能夠提高程序的性能。只有在鎖競爭激烈的情況下,才會進行鎖升級,將鎖升級為輕量級鎖狀態(tài)。
下面是 synchronized 鎖升級的具體流程:
無鎖狀態(tài)當(dāng)一個線程訪問一個同步塊時,如果該同步塊沒有被其他線程占用,那么該線程就可以直接進入同步塊,并且將同步塊標(biāo)記為偏向鎖狀態(tài)。這個過程不需要進行任何加鎖操作,屬于樂觀鎖狀態(tài)。
偏向鎖狀態(tài)在偏向鎖狀態(tài)下,同步塊已經(jīng)被一個線程占用,其他線程訪問該同步塊時,只需要判斷該同步塊是否被當(dāng)前線程占用,如果是,則直接進入同步塊。這個過程不需要進行任何加鎖操作,仍然屬于樂觀鎖狀態(tài)。
輕量級鎖狀態(tài)如果在偏向鎖狀態(tài)下,有多個線程競爭同一個同步塊,那么該同步塊就會升級為輕量級鎖狀態(tài)。此時,每個線程都會在自己的 CPU 緩存中保存該同步塊的副本,并通過 CAS(Compare and Swap)操作來對同步塊進行加鎖和解鎖。這個過程需要進行加鎖操作,但相對于傳統(tǒng)的 mutex 鎖,輕量級鎖的效率要高很多。
重量級鎖狀態(tài)輕量級鎖之后會通過自旋來獲取鎖,自旋執(zhí)行一定次數(shù)之后還未成功獲取到鎖,此時就會升級為重量級鎖,并且進入阻塞狀態(tài)。synchronized 鎖升級的過程可以有效地減少鎖競爭,提高多線程并發(fā)性。
4.i++ 如何保證線程安全?
保證 i++ 線程安全的手段是加鎖,可以通過 synchronized 或 Lock 加鎖來保證 i++ 的線程安全。
5.HashMap和ConcurrentHashMap有什么區(qū)別?
HashMap 和 ConcurrentHashMap 是 Map 接口的具體實現(xiàn),ConcurrentHashMap 可以看作是 HashMap 的線程安全版本,它們的具體區(qū)別如下:
線程安全性:
- HashMap:HashMap 是非線程安全的。如果多個線程同時訪問和修改 HashMap,沒有適當(dāng)?shù)耐綑C制的話,可能會導(dǎo)致不一致的結(jié)果或者拋出 ConcurrentModificationException 異常。
- ConcurrentHashMap:ConcurrentHashMap 是線程安全的。多個線程可以同時讀取和修改 ConcurrentHashMap,而不會導(dǎo)致數(shù)據(jù)不一致或者拋出異常。它使用了一種稱為"分段鎖"(Segmented Locking)的技術(shù),將整個數(shù)據(jù)結(jié)構(gòu)分成多個部分,每個部分都有一個獨立的鎖。這樣,在多線程環(huán)境下,不同的線程可以同時操作不同的部分,從而提高并發(fā)性能。
性能:
- HashMap:HashMap 在單線程環(huán)境下通常具有更好的性能,因為它不需要額外的同步開銷。
- ConcurrentHashMap:ConcurrentHashMap 在高并發(fā)環(huán)境下具有更好的性能,因為它使用了分段鎖技術(shù),多個線程可以同時操作不同的部分,從而減少了競爭和阻塞。
Null 值和 Null 鍵:
- HashMap:HashMap 允許使用 null 作為值和鍵。
- ConcurrentHashMap:ConcurrentHashMap 不允許使用 null 作為鍵,但允許使用 null 作為值。
6.@Autowired和@Resource區(qū)別?
@Autowired 和 @Resource 都是 Spring/Spring Boot 項目中,用來進行依賴注入的注解。它們都提供了將依賴對象注入到當(dāng)前對象的功能,但二者卻有以下不同:
- 來源不同:@Autowired 和 @Resource 來自不同的“父類”,其中 @Autowired 是 Spring 定義的注解,而 @Resource 是 Java 定義的注解,它來自于 JSR-250(Java 250 規(guī)范提案);
- 依賴查找的順序不同:@Autowired 是先根據(jù)類型(byType)查找,如果存在多個 Bean 再根據(jù)名稱(byName)進行查找;而 @Resource 是先根據(jù)名稱查找,如果(根據(jù)名稱)查找不到,再根據(jù)類型進行查找;
- 支持的參數(shù)不同:@Autowired 只支持設(shè)置一個 required 的參數(shù),而 @Resource 支持更多的參數(shù)設(shè)置,@Resource 支持 7 個參數(shù)的設(shè)置;
- 依賴注入的支持不同:@Autowired 支持屬性注入、構(gòu)造方法注入和 Setter 注入,而 @Resource 只支持屬性注入和 Setter 注入;
- 編譯器 IDEA 的提示不同:當(dāng)使用 IDEA 專業(yè)版注入 Mapper 對象時,使用 @Autowired 編譯器會提示報錯信息(雖然報錯但不印象程序的執(zhí)行);而 @Resource 則不會報錯。
7.說說常用的設(shè)計模式
說到設(shè)計模式可以舉一些常見的設(shè)計模式,以及這些設(shè)計模式的具體應(yīng)用,比如以下這些:
- 工廠模式(Factory Pattern):工廠模式是一種創(chuàng)建型設(shè)計模式,它提供了一種創(chuàng)建對象的方式,使得應(yīng)用程序可以更加靈活和可維護。比如在 Spring 中,F(xiàn)actoryBean 就是一個工廠模式的實現(xiàn),使用它的工廠模式就可以創(chuàng)建出來其他的 Bean 對象。
- 單例模式(Singleton Pattern):單例模式是一種創(chuàng)建型設(shè)計模式,它保證一個類只有一個實例,并提供了一個全局訪問點。比如在 Spring 中,所以的 Bean 默認是單例的,這意味著每個 Bean 只會被創(chuàng)建一次,并且可以在整個應(yīng)用程序中共享。
- 代理模式模式(Proxy Pattern):代理模式是一種結(jié)構(gòu)型設(shè)計模式,它允許開發(fā)人員在不修改原有代碼的情況下,向應(yīng)用程序中添加新的功能。比如在 Spring AOP(面向切面編程)就是使用代理模式的實現(xiàn),它允許開發(fā)人員在方法調(diào)用前后執(zhí)行一些自定義的操作,比如日志記錄、性能監(jiān)控等。
- 模板方法模式(Template Pattern):模板方法模式是最常用的設(shè)計模式之一,它是指定義一個操作算法的骨架,而將一些步驟的實現(xiàn)延遲到子類中去實現(xiàn),使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。此模式是基于繼承的思想實現(xiàn)代碼復(fù)用的。比如在 MyBatis 中的典型代表 BaseExecutor,在 MyBatis 中 BaseExecutor 實現(xiàn)了大部分SQL 執(zhí)行的邏輯。
- 觀察者模式(Observer Pattern):定義了一種一對多的依賴關(guān)系,當(dāng)一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都會得到通知并自動更新。比如事件驅(qū)動、消息傳遞等功能時,可以使用觀察者模式,例如 Spring Event 事件機制。
- 適配器模式(Adapter Pattern):適配器模式是一種結(jié)構(gòu)型設(shè)計模式,它允許開發(fā)人員將一個類的接口轉(zhuǎn)換成另一個類的接口,以滿足客戶端的需求。在 Spring 中,適配器模式常用于將不同類型的對象轉(zhuǎn)換成統(tǒng)一的接口,比如將 Servlet API 轉(zhuǎn)換成 Spring MVC 的控制器接口。
8.Redis為什么這么快?
Redis 運行比較快的原因有以下幾個:
- 內(nèi)存存儲:Redis 主要是將數(shù)據(jù)存儲在內(nèi)存中,而不是磁盤上。相比于傳統(tǒng)的磁盤存儲數(shù)據(jù)庫系統(tǒng),內(nèi)存訪問速度更快,因此可以實現(xiàn)更低的延遲和更高的吞吐量。
- 單線程模型:Redis 采用單線程模型來處理客戶端的請求。這意味著不會發(fā)生多線程之間的鎖競爭和上下文切換,避免了由于線程切換而導(dǎo)致的性能損耗。此外,單線程模型使得 Redis 的代碼更加簡單和可預(yù)測。
- 非阻塞I/O:Redis 使用非阻塞 I/O 模型來處理網(wǎng)絡(luò)請求。它通過使用事件驅(qū)動的方式處理并發(fā)連接,充分利用了操作系統(tǒng)提供的異步 I/O 功能。這使得 Redis 能夠高效地處理大量并發(fā)請求,而不會被阻塞。
- 高效的數(shù)據(jù)結(jié)構(gòu):Redis 提供了多種高效的數(shù)據(jù)結(jié)構(gòu),如字符串、哈希表、列表、集合和有序集合等。這些數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中直接存儲和操作數(shù)據(jù),使得Redis能夠以常數(shù)時間復(fù)雜度 (O(1))來執(zhí)行許多常見的操作,如插入、刪除和查找;。
- 異步操作:Redis 支持異步操作,可以將一些耗時的操作(如持久化)放到后臺進行,不會阻塞其他的操作。
- 輕量級:Redis 本身是一個非常輕量級的軟件,它使用 C 語言編寫,代碼簡潔高效。它沒有復(fù)雜的依賴和額外的抽象層,因此可以更快地啟動和運行。
9.索引的種類?如何優(yōu)化?
MySQL 索引根據(jù)不同的維度可以分為不同類型,比如以下這些:
- 根據(jù)數(shù)據(jù)結(jié)構(gòu)分類可分為:B+ tree 索引、Hash 索引、Full-Text 索引。
- 根據(jù)物理存儲分類可分為:聚簇索引、二級索引(輔助索引、非聚簇索引)。
- 根據(jù)字段特性分類可分為:主鍵索引、普通索引、唯一索引、前綴索引。
- 根據(jù)字段個數(shù)分類可分為:單列索引、聯(lián)合索引(復(fù)合索引、組合索引)。
索引優(yōu)化
索引優(yōu)化可以從以下幾個方面入手:
- 選擇適當(dāng)?shù)乃饕愋?/strong>:MySQL 提供了不同類型的索引,包括 B-tree、哈希、全文等。根據(jù)查詢的特點和數(shù)據(jù)的特性,選擇合適的索引類型。B-tree 索引是最常用的索引類型,適用于范圍查詢和排序操作。
- 選擇合適的索引列:選擇對查詢頻率高且選擇性好的列作為索引列。選擇性是指索引列中不重復(fù)值的比例,選擇性越高,索引的效果越好。避免在索引中包含過多重復(fù)值或過長的列。
- 盡量使用聚簇索引:聚簇索引的葉子節(jié)點存儲了具體的數(shù)據(jù),不用在像非聚簇索引一樣進行回表查詢,所以在查詢時,盡量選擇聚簇索引。
- 避免過多的索引:索引會占用存儲空間,并且在數(shù)據(jù)更新時需要維護索引,過多的索引會增加維護的開銷。只創(chuàng)建必要的索引,避免創(chuàng)建過多的索引。
- 使用索引提示:在某些情況下,MySQL 的查詢優(yōu)化器可能選擇了不理想的查詢計劃。可以使用索引提示(Index Hint)來指導(dǎo)優(yōu)化器選擇正確的索引。
- 定期監(jiān)控和優(yōu)化:持續(xù)監(jiān)控數(shù)據(jù)庫的性能指標(biāo),如查詢執(zhí)行時間、索引使用情況等。根據(jù)監(jiān)控結(jié)果,對索引進行調(diào)整和優(yōu)化,以保持數(shù)據(jù)庫的高性能。
10.算法題:合并重疊區(qū)間
解題思路和實現(xiàn)代碼參考:https://leetcode.cn/problems/merge-intervals/solution/he-bing-qu-jian-by-leetcode-solution/。
小結(jié)
從上面的題可以看出來,團子的整體面試題是不難的,可以說社招的面試難度,現(xiàn)在是小于校招的面試難度的,這也可能和校招龐大的競爭者群體有關(guān)。
所以如果是社招的哥們,也可以定期騎驢找馬試試水,一是檢驗自己能力是否已經(jīng)落后與用人市場;二是,萬一有驚喜,拿到更好的 offer,也就開啟了職場的新篇章。