近期面試回憶+復(fù)習(xí)版
求問紅色部分如何回答
- 一個線程等待另一個執(zhí)行完了,再執(zhí)行 CountDownLatch
- SPI機制,接口傳參有requestid嘛 DubboFilter可否用AOP實現(xiàn)
- AOP原理 使用場景 在項目中有用到過嗎
- 實現(xiàn)動態(tài)代理的方式
- 類的加載過程
- Bean的生命周期
- 實現(xiàn)分布式ID的幾種方式
- JAVA是值傳遞,會變
- MQ消息堆積,加機器依然不能處理 怎么解決? MQ消息重復(fù)保障:消息唯一ID,消息表記錄,先判斷在處理。版本號?
- MybatisPlus使用時有啥坑
- Spring常用注解
- Redis數(shù)據(jù)結(jié)構(gòu)使用時注意事項
- Redis做接口超時重試的業(yè)務(wù)冪等,存多久?<唯一ID,接口創(chuàng)建的對象>存緩存1分鐘,那超時1分鐘后呢,就無法冪等了?接口冪等咋保證
- 單機限流和集群限流區(qū)別?為何選了單機
- Ratelimiter原理
- ThreadLocal注意事項 TransmittableThreadLocal跨線程傳遞原理知道嘛
- GC
- GC Root 哪個區(qū)域可以作為
- 垃圾回收的區(qū)域
- 線程池介紹。核心線程和非核心線程本質(zhì)沒區(qū)別
- 線程池類型
- Redisson分布式鎖特點
- Kafka RMQ對比,Kafka了解嘛特別的
- 異常處理方式
a. 大量的try {...} catch {...} finally {...}
?代碼塊與處理業(yè)務(wù)的代碼攪合在一起,冗余+還影響代碼的可讀性。
@ExceptionHandler+@ControllerAdvice的全局異常處理方式
b. ExceptionHandler異常處理器,實際作用:如果在某個Controller類中定義一個異常處理方法,并在方法上添加該注解,當(dāng)出現(xiàn)指定的異常時,會執(zhí)行該處理異常的方法。每個Controller類都有對每種異常處理的方法——代碼冗余
c. 注解(@ControllerAdvice)可以把異常處理器(被@ExceptionHandler注解的方法)應(yīng)用到所有的Controller,而不是單單的一個Controller類。
手撕:
- 最多刪除一個字符,看是不是回文串
- 兩個線程依次打印數(shù)字和字母
- 反轉(zhuǎn)從left到right的鏈表
synchronized
?+?wait/notify;
CountDownLatch
?ReentrantLock
?的條件變量(Condition
)
ForkJoinPool:拆分大任務(wù)并行處理(適合計算密集型)
2. SPI
即 Service Provider lnterface,是一種服務(wù)發(fā)現(xiàn)機制 專門提供給 擴展框架的開發(fā)者去使用的一個接口。 SPI本身只是一個接口,它定義了服務(wù)的標準,但不提供具體的實現(xiàn)。允許開發(fā)者自己實現(xiàn)一個具體的實現(xiàn)類來 將自己的實現(xiàn)注冊到服務(wù)發(fā)現(xiàn)機制中?;诜瓷渌枷肟梢栽谶\行時動態(tài)加載不同的服務(wù)實現(xiàn)。通過實現(xiàn) org.apache.dubbo.rpc.Filter 接口來擴展 Dubbo 的過濾機制。進行請求前后的處理操作(例如:日志記錄、鑒權(quán)、修改返回值等)。配置好這些過濾器后,Dubbo 會自動加載并應(yīng)用這些自定義的過濾器,幫助你在 Dubbo 的調(diào)用鏈中插入自定義邏輯。
使用 Dubbo Filter 處理與 RPC 調(diào)用鏈相關(guān)的邏輯(如超時、重試、RPC隱式傳參),用 AOP 處理純業(yè)務(wù)邏輯(如數(shù)據(jù)庫事務(wù)、日志記錄)。
?Spring AOP 和 AspectJ實現(xiàn):
@Aspect @Component public class DubboAspect { // 攔截所有 Dubbo 服務(wù)接口的方法 @Pointcut("execution(* com.example.dubbo.service.*.*(..))") public void dubboService() {} @Around("dubboService()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { // 前置處理 System.out.println("Request: " + joinPoint.getSignature().getName()); // 執(zhí)行方法 Object result = joinPoint.proceed(); // 后置處理 System.out.println("Response: " + result); return result; } }
3.事務(wù)就是AOP 實習(xí)中用到AOP
26.聲明式事務(wù)實現(xiàn)原理了解嗎?
Spring 的聲明式事務(wù)管理是通過 AOP(面向切面編程)和代理機制實現(xiàn)的。
第一步,在 Bean 初始化階段創(chuàng)建代理對象:
Spring 容器在初始化單例 Bean 的時候,會遍歷所有的 BeanPostProcessor 實現(xiàn)類,并執(zhí)行其 postProcessAfterInitialization 方法。
在執(zhí)行 postProcessAfterInitialization 方法時會遍歷容器中所有的切面,查找與當(dāng)前 Bean 匹配的切面,這里會獲取事務(wù)的屬性切面,也就是?@Transactional
?注解及其屬性值。
然后根據(jù)得到的切面創(chuàng)建一個代理對象,默認使用 JDK 動態(tài)代理創(chuàng)建代理,如果目標類是接口,則使用 JDK 動態(tài)代理,否則使用 Cglib。
第二步,在執(zhí)行目標方法時進行事務(wù)增強操作:
當(dāng)通過代理對象調(diào)用 Bean 方法的時候,會觸發(fā)對應(yīng)的 AOP 增強攔截器,聲明式事務(wù)是一種環(huán)繞增強. 事務(wù)攔截器TransactionInterceptor
在invoke
方法中,通過調(diào)用父類TransactionAspectSupport
的invokeWithinTransaction
方法進行事務(wù)處理,包括開啟事務(wù)、事務(wù)提交、異?;貪L等。
- 接口的方法定義是公開且明確的,代理類只需實現(xiàn)接口的所有方法。
- 類的代理涉及更復(fù)雜的邏輯(如繼承、方法覆蓋、構(gòu)造函數(shù)等),而JDK動態(tài)代理未對此提供支持。
- 反射:用于檢查和操作類的方法和字段,動態(tài)調(diào)用方法或訪問字段。反射是 Java 提供的內(nèi)置機制,直接操作類對象。
- 動態(tài)代理:通過生成代理類來攔截方法調(diào)用,通常用于 AOP 實現(xiàn)。動態(tài)代理使用反射來調(diào)用被代理的方法。
- 實例化:Spring 首先使用構(gòu)造方法或者工廠方法創(chuàng)建一個 Bean 的實例。在這個階段,Bean 只是一個空的 Java 對象,還未設(shè)置任何屬性。
- 屬性賦值:Spring 將配置文件中的屬性值或依賴的 Bean 注入到該 Bean 中。這個過程稱為依賴注入,確保 Bean 所需的所有依賴都被注入。
- 初始化:Spring 調(diào)用 afterPropertiesSet 方法,或通過配置文件指定的 init-method 方法,完成初始化。
- 使用中:Bean 準備好可以使用了。
- 銷毀:在容器關(guān)閉時,Spring 會調(diào)用 destroy 方法,完成 Bean 的清理工作。
- partition 并行處理,寫入數(shù)據(jù)的時候由于單個Partion是末尾添加所以速度最優(yōu)。
- 順序?qū)懭?,磁盤順序讀寫速度超過內(nèi)存隨機讀寫,充分利用磁盤特性。
- 批量壓縮文件,利用了現(xiàn)代操作系統(tǒng)分頁存儲 Page Cache 來利用內(nèi)存提高 I/O 效率。
- 通過mmap實現(xiàn)順序的快速寫入,提高I/O速度。
- 讀取數(shù)據(jù)時采用 sendfile,減少 CPU 消耗。
4. jdk動態(tài)代理只能代理接口:
a. Java語言不支持類的多重繼承,而JDK動態(tài)代理生成的代理類已經(jīng)繼承了java.lang.reflect.Proxy
類。
b. JDK動態(tài)代理通過Proxy.newProxyInstance()
生成代理類時,需傳入接口列表,而非類:
JDK動態(tài)代理的設(shè)計初衷是通過接口實現(xiàn)松耦合的代理:
使用 JDK 動態(tài)代理時,Spring AOP 會創(chuàng)建一個代理對象,該代理對象實現(xiàn)了目標對象所實現(xiàn)的接口,并在方法調(diào)用前后插入橫切邏輯。
CGLIB 動態(tài)代理是基于繼承的代理,可以代理沒有實現(xiàn)接口的類。
使用 CGLIB 動態(tài)代理時,Spring AOP 會生成目標類的子類,并在方法調(diào)用前后插入橫切邏輯。
40.說說 AOP 和反射的區(qū)別?(補充)
Spring上下文中的Bean生命周期
首先說一下Servlet的生命周期:實例化,初始init,接收請求service,銷毀destroy;
Spring上下文中的Bean生命周期也類似,如下:
(1)實例化Bean:
對于BeanFactory容器,當(dāng)客戶向容器請求一個尚未初始化的bean時,或初始化bean的時候需要注入另一個尚未初始化的依賴時,容器就會調(diào)用createBean進行實例化。對于ApplicationContext容器,當(dāng)容器啟動結(jié)束后,通過獲取BeanDefifinition對象中的信息,實例化所有的bean。
(2)設(shè)置對象屬性(依賴注入):
實例化后的對象被封裝在BeanWrapper對象中,緊接著,Spring根據(jù)BeanDefifinition中的信息 以 及 通過BeanWrapper提供的設(shè)置屬性的接口完成依賴注入。
(3)處理Aware接口:
接著,Spring會檢測該對象是否實現(xiàn)了xxxAware接口,并將相關(guān)的xxxAware實例注入給Bean:
①如果這個Bean已經(jīng)實現(xiàn)了BeanNameAware接口,會調(diào)用它實現(xiàn)的setBeanName(String beanId)方法,此處傳遞的就是Spring配置文件中Bean的id值;
②如果這個Bean已經(jīng)實現(xiàn)了BeanFactoryAware接口,會調(diào)用它實現(xiàn)的setBeanFactory()方法,傳遞的是Spring工廠自身。
③如果這個Bean已經(jīng)實現(xiàn)了ApplicationContextAware接口,會調(diào)用setApplicationContext(ApplicationContext)方法,傳入Spring上下文;
MQ重復(fù)消費的原因:
消費者接收消息后,在確認之前斷開了連接或者取消訂閱消息會被重新分發(fā)給下一個訂閱的消費者 1. 在發(fā)送消息時,為消息設(shè)置一個唯一的 ID??梢允褂?/span> UUID 作為 ID,確保唯一性。 2. 在消費者端,判斷是否處理過相同 ID 的消息??梢越柚?/span> Redis 等緩存數(shù)據(jù)庫記錄已處理過的消息ID,每次接收到新消息時,先查詢緩存中是否存在相同 ID 的消息。
Kafka
能夠處理大量的消息流,適合實時處理、實時分析等場景。而RabbitMQ則更適合處理少量但更加復(fù)雜的消息。kafka的快是從底層設(shè)計,到充分利用硬件,系統(tǒng),壓縮等等特性,綜合產(chǎn)生的結(jié)果。
- Spring Boot 內(nèi)嵌了 Tomcat、Jetty、Undertow 等容器,直接運行 jar 包就可以啟動項目。
- Spring Boot 內(nèi)置了 Starter 和自動裝配,避免繁瑣的手動配置。例如,如果項目中添加了 spring-boot-starter-web,Spring Boot 會自動配置 Tomcat 和 Spring MVC。
- Spring Boot 內(nèi)置了 Actuator 和 DevTools,便于調(diào)試和監(jiān)控。
- 第一步,創(chuàng)建 SpringApplication 實例,負責(zé)應(yīng)用的啟動和初始化;
- 第二步,從 application.yml 中加載配置文件和環(huán)境變量;
- 第三步,創(chuàng)建上下文環(huán)境 ApplicationContext,并加載 Bean,完成依賴注入;
- 第四步,啟動內(nèi)嵌的 Web 容器。
- 第五步,發(fā)布啟動完成事件 ApplicationReadyEvent,并調(diào)用 ApplicationRunner 的 run 方法完成啟動后的邏輯。
7.分布式ID:
UUID 128位太長,不是有序的 數(shù)據(jù)庫索引爆炸 完全無序無法分頁查詢
mysql幾臺起始值不同,間隔相同,但是擴展難,QPS超500就不行
號段模式,一次DB批量請求1000個 美團Leaf 百度UidGenerator
redis自增
改造雪花;Leaf引入zookeeper解決機器ID分配問題 時鐘回撥如何解決
12.Redis數(shù)據(jù)結(jié)構(gòu)注意事項:
腦子轉(zhuǎn)不過來想問啥 刷到帖子可以是 1.key設(shè)置要業(yè)務(wù)相關(guān) 2.避免Redis大key 熱key key分片,本地緩存 限流
JVM
* Redisson分布式鎖
是可重入鎖:利用hash結(jié)構(gòu)記錄線程id和重入次數(shù) 避免死鎖 《----釋放鎖時候,通過lua腳本判斷是持有鎖的線程id和次數(shù)>0
Redisson提供的紅鎖來解決這個問題,它的主要作用是,不能只在一個Redis實例上創(chuàng)建鎖,應(yīng)該是在多個Redis實例上創(chuàng)建鎖,并且要求在大多數(shù)Redis節(jié)點上都成功創(chuàng)建鎖,紅鎖中要求是Redis的節(jié)點數(shù)量要過半。這樣就能避免線程1加鎖成功后master節(jié)點宕機導(dǎo)致線程2成功加鎖到新的master節(jié)點上的問題了。
> Redisson實現(xiàn)的分布式鎖能解決主從一致性的問題嗎? 候選人:這個是不能的。比如,當(dāng)線程1加鎖成功后,master節(jié)點數(shù)據(jù)會異步復(fù)制到slave節(jié)點,此時如果當(dāng)前持有Redis鎖的master節(jié)點宕機,slave節(jié)點被提升為新的master節(jié)點,假如現(xiàn)在來了一個線程2,再次加鎖,會在新的master節(jié)點上加鎖成功,這個時候就會出現(xiàn)兩個節(jié)點同時持有一把鎖的問題。
redis本身就不是強一致的,zookeeper分布式鎖強一致
自動續(xù)期:看門狗每10s檢測是否被當(dāng)前線程所持有,持有則續(xù)期30s (鎖提前釋放 線程安全問題)
讀寫鎖
公平鎖
* 線程池種類:
a. 動態(tài)線程池,可以自定義拒絕策略等 提供了“延遲”和“周期執(zhí)行”功能的ThreadPoolExecutor
b. 用Executors創(chuàng)建線程池 newFixedThreadPool:固定線程數(shù)量。核心線程數(shù)與最大線程數(shù)一樣,沒有救急線程。阻塞隊列是LinkedBlockingQueue,最大容量為Integer.MAX_VALUE
c. newSingleThreadExecutor:單線程
d. newCachedThreadPool:可緩存線程池 過期時間是60s 適合任務(wù)數(shù)比較密集,但每個任務(wù)執(zhí)行時間較短的情況 核心線程數(shù)為0 最大線程數(shù)是Integer.MAX_VALUE(造成OOM)
拒絕策略
* ThreadLocal
Entry對象中的Key是使用弱引用的ThreadLocal實例,也就是ThreadLocal對象可以被GC自動回收,但是對應(yīng)的value(線程變量的副本)還在被引用,所以,value是不能被GC自動回收的,這種情況下就會存在內(nèi)存泄露的風(fēng)險。
我們再來總結(jié)下,在線程池中使用ThreadLocal保存數(shù)據(jù)存在內(nèi)存泄露風(fēng)險的原因:線程池中的核心線程會被循環(huán)使用,每個線程中對應(yīng)的ThreadLocalMap會被線程強引用。
## 多線程交替打印字母和數(shù)字
private static final Object lock = new Object(); private static int currentThread = 1; // 1 for A, 2 for B, 3 for C public static void main(String[] args) { Thread threadA = new Thread(new PrintTask("A", 1)); Thread threadB = new Thread(new PrintTask("1", 2)); threadA.start(); threadB.start(); } static class PrintTask implements Runnable { private String content; private int threadNumber; public PrintTask(String content, int threadNumber) { this.content = content; this.threadNumber = threadNumber; } @Override public void run() { for (int i = 0; i < 10; i++) { // 打印10次 synchronized (lock) { while (currentThread != threadNumber) { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.println(content); currentThread = currentThread % 2 + 1; // 更新下一個要執(zhí)行的線程 lock.notifyAll(); } } } }
Spring Boot 的優(yōu)點非常多,比如說:
34.Spring Boot 啟動原理了解嗎?
Spring Boot 的啟動由 SpringApplication 類負責(zé):
Spring MVC 是基于 Spring 框架的一個模塊,提供了一種 Model-View-Controller(模型-視圖-控制器)的開發(fā)模式。
異常
* 遇到的異常:OOM --->我講了一次例子,被抑制說表有多大造成OOM 沒關(guān)注機器的配置堆內(nèi)存參數(shù)嘛 下次講存地圖的List+log輸出造成OOM:工具定位+代碼檢查
BeanCreationException
原因:Bean 創(chuàng)建失敗,通常是由于依賴注入問題或配置錯誤。
解決方法:檢查 Bean 的依賴是否正確注入。確保配置文件或注解配置正確。查看堆棧信息,定位具體問題。
NoSuchBeanDefinitionException
原因:Spring 容器中找不到指定的 Bean。
解決方法:檢查 Bean 是否被正確掃描或配置。確保 @Component、@Service、@Repository 等注解正確使用。檢查包掃描路徑是否正確配置。
HttpMessageNotReadableException
原因:HTTP 請求體解析失敗,通常是由于 JSON 格式錯誤或類型不匹配。
解決方法:檢查請求體的 JSON 格式是否正確。確保請求參數(shù)與后端類型匹配。
MethodArgumentNotValidException
原因:方法參數(shù)校驗失敗,通常是由于 @Valid 注解校驗不通過。
解決方法:檢查校驗注解(如 @NotNull、@Size)的使用是否正確。確保請求參數(shù)符合校驗規(guī)則。