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