近期面试回忆+复习版
求问红色部分如何回答
- 一个线程等待另一个执行完了,再执行 CountDownLatch
- SPI机制,接口传参有requestid嘛 DubboFilter可否用AOP实现
- AOP原理 使用场景 在项目中有用到过吗
- 实现动态代理的方式
- 类的加载过程
- Bean的生命周期
- 实现分布式ID的几种方式
- JAVA是值传递,会变
- MQ消息堆积,加机器依然不能处理 怎么解决? MQ消息重复保障:消息唯一ID,消息表记录,先判断在处理。版本号?
- MybatisPlus使用时有啥坑
- Spring常用注解
- Redis数据结构使用时注意事项
- Redis做接口超时重试的业务幂等,存多久?<唯一ID,接口创建的对象>存缓存1分钟,那超时1分钟后呢,就无法幂等了?接口幂等咋保证
- 单机限流和集群限流区别?为何选了单机
- Ratelimiter原理
- ThreadLocal注意事项 TransmittableThreadLocal跨线程传递原理知道嘛
- GC
- GC Root 哪个区域可以作为
- 垃圾回收的区域
- 线程池介绍。核心线程和非核心线程本质没区别
- 线程池类型
- Redisson分布式锁特点
- Kafka RMQ对比,Kafka了解嘛特别的
- 异常处理方式
a. 大量的try {...} catch {...} finally {...}
代码块与处理业务的代码搅合在一起,冗余+还影响代码的可读性。
@ExceptionHandler+@ControllerAdvice的全局异常处理方式
b. ExceptionHandler异常处理器,实际作用:如果在某个Controller类中定义一个异常处理方法,并在方法上添加该注解,当出现指定的异常时,会执行该处理异常的方法。每个Controller类都有对每种异常处理的方法——代码冗余
c. 注解(@ControllerAdvice)可以把异常处理器(被@ExceptionHandler注解的方法)应用到所有的Controller,而不是单单的一个Controller类。
手撕:
- 最多删除一个字符,看是不是回文串
- 两个线程依次打印数字和字母
- 反转从left到right的链表
synchronized
+wait/notify;
CountDownLatch
ReentrantLock
的条件变量(Condition
)
ForkJoinPool:拆分大任务并行处理(适合计算密集型)
2. SPI
即 Service Provider lnterface,是一种服务发现机制 专门提供给 扩展框架的开发者去使用的一个接口。 SPI本身只是一个接口,它定义了服务的标准,但不提供具体的实现。允许开发者自己实现一个具体的实现类来 将自己的实现注册到服务发现机制中。基于反射思想可以在运行时动态加载不同的服务实现。通过实现 org.apache.dubbo.rpc.Filter 接口来扩展 Dubbo 的过滤机制。进行请求前后的处理操作(例如:日志记录、鉴权、修改返回值等)。配置好这些过滤器后,Dubbo 会自动加载并应用这些自定义的过滤器,帮助你在 Dubbo 的调用链中插入自定义逻辑。
使用 Dubbo Filter 处理与 RPC 调用链相关的逻辑(如超时、重试、RPC隐式传参),用 AOP 处理纯业务逻辑(如数据库事务、日志记录)。
Spring AOP 和 AspectJ实现:
@Aspect @Component public class DubboAspect { // 拦截所有 Dubbo 服务接口的方法 @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()); // 执行方法 Object result = joinPoint.proceed(); // 后置处理 System.out.println("Response: " + result); return result; } }
3.事务就是AOP 实习中用到AOP
- 接口的方法定义是公开且明确的,代理类只需实现接口的所有方法。
- 类的代理涉及更复杂的逻辑(如继承、方法覆盖、构造函数等),而JDK动态代理未对此提供支持。
- 实例化:Spring 首先使用构造方法或者工厂方法创建一个 Bean 的实例。在这个阶段,Bean 只是一个空的 Java 对象,还未设置任何属性。
- 属性赋值:Spring 将配置文件中的属性值或依赖的 Bean 注入到该 Bean 中。这个过程称为依赖注入,确保 Bean 所需的所有依赖都被注入。
- 初始化:Spring 调用 afterPropertiesSet 方法,或通过配置文件指定的 init-method 方法,完成初始化。
- 使用中:Bean 准备好可以使用了。
- 销毁:在容器关闭时,Spring 会调用 destroy 方法,完成 Bean 的清理工作。
- partition 并行处理,写入数据的时候由于单个Partion是末尾添加所以速度最优。
- 顺序写入,磁盘顺序读写速度超过内存随机读写,充分利用磁盘特性。
- 批量压缩文件,利用了现代操作系统分页存储 Page Cache 来利用内存提高 I/O 效率。
- 通过mmap实现顺序的快速写入,提高I/O速度。
- 读取数据时采用 sendfile,减少 CPU 消耗。
4. jdk动态代理只能代理接口:
a. Java语言不支持类的多重继承,而JDK动态代理生成的代理类已经继承了java.lang.reflect.Proxy
类。
b. JDK动态代理通过Proxy.newProxyInstance()
生成代理类时,需传入接口列表,而非类:
JDK动态代理的设计初衷是通过接口实现松耦合的代理:
Spring上下文中的Bean生命周期
首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;
Spring上下文中的Bean生命周期也类似,如下:
(1)实例化Bean:
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefifinition对象中的信息,实例化所有的bean。
(2)设置对象属性(依赖注入):
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefifinition中的信息 以 及 通过BeanWrapper提供的设置属性的接口完成依赖注入。
(3)处理Aware接口:
接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;
②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
MQ重复消费的原因:
消费者接收消息后,在确认之前断开了连接或者取消订阅消息会被重新分发给下一个订阅的消费者 1. 在发送消息时,为消息设置一个唯一的 ID。可以使用 UUID 作为 ID,确保唯一性。 2. 在消费者端,判断是否处理过相同 ID 的消息。可以借助 Redis 等缓存数据库记录已处理过的消息ID,每次接收到新消息时,先查询缓存中是否存在相同 ID 的消息。
Kafka
能够处理大量的消息流,适合实时处理、实时分析等场景。而RabbitMQ则更适合处理少量但更加复杂的消息。kafka的快是从底层设计,到充分利用硬件,系统,压缩等等特性,综合产生的结果。
7.分布式ID:
UUID 128位太长,不是有序的 数据库索引爆炸 完全无序无法分页查询
mysql几台起始值不同,间隔相同,但是扩展难,QPS超500就不行
号段模式,一次DB批量请求1000个 美团Leaf 百度UidGenerator
redis自增
改造雪花;Leaf引入zookeeper解决机器ID分配问题 时钟回拨如何解决
12.Redis数据结构注意事项:
脑子转不过来想问啥 刷到帖子可以是 1.key设置要业务相关 2.避免Redis大key 热key key分片,本地缓存 限流
JVM
* Redisson分布式锁
是可重入锁:利用hash结构记录线程id和重入次数 避免死锁 《----释放锁时候,通过lua脚本判断是持有锁的线程id和次数>0
Redisson提供的红锁来解决这个问题,它的主要作用是,不能只在一个Redis实例上创建锁,应该是在多个Redis实例上创建锁,并且要求在大多数Redis节点上都成功创建锁,红锁中要求是Redis的节点数量要过半。这样就能避免线程1加锁成功后master节点宕机导致线程2成功加锁到新的master节点上的问题了。
> Redisson实现的分布式锁能解决主从一致性的问题吗? 候选人:这个是不能的。比如,当线程1加锁成功后,master节点数据会异步复制到slave节点,此时如果当前持有Redis锁的master节点宕机,slave节点被提升为新的master节点,假如现在来了一个线程2,再次加锁,会在新的master节点上加锁成功,这个时候就会出现两个节点同时持有一把锁的问题。
redis本身就不是强一致的,zookeeper分布式锁强一致
自动续期:看门狗每10s检测是否被当前线程所持有,持有则续期30s (锁提前释放 线程安全问题)
读写锁
公平锁
* 线程池种类:
a. 动态线程池,可以自定义拒绝策略等 提供了“延迟”和“周期执行”功能的ThreadPoolExecutor
b. 用Executors创建线程池 newFixedThreadPool:固定线程数量。核心线程数与最大线程数一样,没有救急线程。阻塞队列是LinkedBlockingQueue,最大容量为Integer.MAX_VALUE
c. newSingleThreadExecutor:单线程
d. newCachedThreadPool:可缓存线程池 过期时间是60s 适合任务数比较密集,但每个任务执行时间较短的情况 核心线程数为0 最大线程数是Integer.MAX_VALUE(造成OOM)
拒绝策略
* ThreadLocal
Entry对象中的Key是使用弱引用的ThreadLocal实例,也就是ThreadLocal对象可以被GC自动回收,但是对应的value(线程变量的副本)还在被引用,所以,value是不能被GC自动回收的,这种情况下就会存在内存泄露的风险。
我们再来总结下,在线程池中使用ThreadLocal保存数据存在内存泄露风险的原因:线程池中的核心线程会被循环使用,每个线程中对应的ThreadLocalMap会被线程强引用。
## 多线程交替打印字母和数字
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; // 更新下一个要执行的线程 lock.notifyAll(); } } } }
异常
* 遇到的异常:OOM --->我讲了一次例子,被抑制说表有多大造成OOM 没关注机器的配置堆内存参数嘛 下次讲存地图的List+log输出造成OOM:工具定位+代码检查
BeanCreationException
原因:Bean 创建失败,通常是由于依赖注入问题或配置错误。
解决方法:检查 Bean 的依赖是否正确注入。确保配置文件或注解配置正确。查看堆栈信息,定位具体问题。
NoSuchBeanDefinitionException
原因:Spring 容器中找不到指定的 Bean。
解决方法:检查 Bean 是否被正确扫描或配置。确保 @Component、@Service、@Repository 等注解正确使用。检查包扫描路径是否正确配置。
HttpMessageNotReadableException
原因:HTTP 请求体解析失败,通常是由于 JSON 格式错误或类型不匹配。
解决方法:检查请求体的 JSON 格式是否正确。确保请求参数与后端类型匹配。
MethodArgumentNotValidException
原因:方法参数校验失败,通常是由于 @Valid 注解校验不通过。
解决方法:检查校验注解(如 @NotNull、@Size)的使用是否正确。确保请求参数符合校验规则。