其他总结
1.介绍下Java集合Fail-Fast、Fail-Safe机制
(1)Fail-Fast(快速失败)
定义:
在集合迭代过程中,如果检测到结构被修改(如增删元素),立即抛出 ConcurrentModificationException,终止操作。
实现原理:
modCount 机制:集合内部维护一个 modCount(修改计数器),每次结构修改(如 add、remove)时递增。
迭代器检查:迭代器初始化时记录当前 modCount 为 expectedModCount,每次调用 next() 或 remove() 时检查是否一致。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
适用集合:非线程安全的集合:如 ArrayList、HashMap、LinkedList。
特点:快速发现错误:适用于单线程开发环境,帮助开发者尽早发现并发修改问题。不保证线程安全:仅作为错误检测机制,不解决多线程安全问题。
其他注意点:Iterator.remove() 是迭代器自身的方法,它在删除元素后会主动同步 modCount 和 expectedModCount,确保两者一致,从而避免触发异常。
(2)Fail-Safe(安全失败)
定义:
在集合迭代过程中,允许结构修改,但迭代器基于原始数据副本或并发控制机制工作,不会抛出异常。
实现原理:
数据副本:迭代器遍历集合的副本(如 CopyOnWriteArrayList),原集合的修改不影响当前迭代。
并发控制:使用线程安全结构(如 ConcurrentHashMap 的分段锁),保证迭代过程中操作的安全性。
适用集合:
线程安全的并发集合:如 CopyOnWriteArrayList、ConcurrentHashMap。
List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "b"));
Iterator<String> it = list.iterator();
list.add("c"); // 修改原集合
it.forEachRemaining(System.out::println); // 输出 "a", "b"(不包含"c")
特点:
无异常抛出:适合多线程场景,允许并发修改。
数据一致性弱:可能读取到过时数据(如 CopyOnWriteArrayList 的迭代器基于旧副本)。
2.HashMap底层实现:
(1)JDK 1.8前:数组+链表(链表过长时查询效率退化到O(n))。
JDK 1.8+:数组+链表/红黑树(链表长度≥8时转红黑树,查询效率优化到O(logn))。
(2)核心机制:哈希计算:(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)(高位异或减少哈希冲突)。
扩容:默认容量16,负载因子0.75,扩容时容量翻倍(resize()触发链表/树节点拆分)。
线程不安全场景:多线程同时触发扩容可能导致链表成环(JDK 1.7)或数据丢失。
(3)对比ConcurrentHashMap:
分段锁(JDK 1.7):锁粒度粗,性能较差。
CAS+synchronized(JDK 1.8):锁单个数组元素(头节点),支持更高并发。
(3)AOP(面向切面编程)的原理详解
1. 核心思想
AOP 通过动态代理技术,将与业务无关的横切关注点(如日志、事务、权限)从业务代码中分离,实现解耦和代码复用。
AOP的增强通常是通过切面+切点+通知来完成的, 在创建bean的时候发现bean和切点表达式匹配就会创建动态代理。而事务内置一个增强类, 在创建bean的时候, 一旦发现你的类加了@Transactional注解 就会创建动态代理。在执行AOP的bean时会先执行动态代理的增强类, 通过责任链分别按顺序执行通知。在执行事务的bean的时候会先执行动态代理的增强类, 在执行目标方法前进行异常捕捉,出现异常回滚事务, 无异常提交事务。
AOP 的底层基于两种代理模式:
JDK 动态代理
适用场景:目标类实现了接口。
实现流程:
通过 Proxy.newProxyInstance() 生成代理对象。
代理类实现 InvocationHandler 接口,在 invoke() 方法中织入增强逻辑。
public class JdkProxy implements InvocationHandler {
private Object target; // 目标对象
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强(如开启事务)
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args); // 执行目标方法
// 后置增强(如提交事务)
System.out.println("After method: " + method.getName());
return result;
}
}
(2)CGLIB 动态代理
适用场景:目标类未实现接口。CGLIB使用ASM(一个Java字节码操作框架)来生成字节码。因为要继承某个类使用了final无法进行继承
实现流程:
通过 Enhancer 生成目标类的子类代理。
实现 MethodInterceptor 接口,在 intercept() 方法中织入增强逻辑。
public class CglibProxy implements MethodInterceptor {
public Object getProxy(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz); // 设置父类
enhancer.setCallback(this); // 设置回调
return enhancer.create(); // 生成代理对象
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置增强
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
// 后置增强
System.out.println("After method: " + method.getName");
return result;
}
}
4.JDK动态代理和CGLIB动态代理的区别
(1)从性能上特性对比:
JDK动态代理要求目标对象必须实现至少一个接口,因为它基于接口生成代理类。而CGLIB动态代理不依赖于目标对象是否实现接口,可以代理没有实现接口的类,它通过继承或者代理目标对象的父类来实现代理。
(2)从创建代理时的性能对比:
JDK动态代理通常比CGLIB动态代理创建速度更快,因为它不需要生成字节码文件。而CGLIB动态代理的创建速度通常比较慢,因为它需要生成字节码文件。另外,JDK代理生成的代理类较小,占用较少的内存,而CGLIB生成的代理类通常较大,占用更多的内存。
(3)从调用时的性能对比:
JDK动态代理在方法调用时需要通过反射机制来调用目标方法,因此性能略低于CGLIB(调用父类),尽管JDK动态代理在Java 8中有了性能改进,但CGLIB动态代理仍然具有更高的方法调用性能。CGLIB动态代理在方法调用时不需要通过反射,直接调用目标方法,通常具有更高的方法调用性能,同时无需类型转换。
5.Spring是如何解决Bean的循环依赖?
Spring是如何解决的循环依赖: 采用三级缓存解决的 就是三个Map ; 关键: 一定要有一个缓存保存它的早期对象作为死循环的出口
(1)一级缓存singletonObjects存完整单例bean。
(2)二级缓存earlySingletonObjects存放的是早期的bean,即半成品,此时还无法使用(只用于循环依赖提供的临时bean对象)。
(3)三级缓存singletonFactories (循环依赖的出口,解决了循环依赖)。它存的是一个对象工厂,用于创建对象并放入二级缓存中。同时,如果对象有Aop代理,则对象工厂返回代理对象
6. 索引,什么场景适合/不适合创建索引
(1)适合创建索引的场景
频繁的查询操作:如果某个列经常用于查询条件(如 WHERE 子句),则创建索引可以提高查询速度。
大数据量表:在大表中,索引可以显著减少数据扫描的时间。
排序和分组:如果经常需要对某个列进行排序(ORDER BY)或分组(GROUP BY),索引可以加速这些操作。
多表连接用的字段适合建立索引
(2)不适合创建索引的场景
频繁的写操作:如果表中数据频繁更新、插入或删除,维护索引的开销可能会影响性能。
小数据量表:对于小表,索引的维护成本可能超过其带来的查询性能提升。
区分度不高的列:如果某个列的不同值很少(例如性别列),则创建索引的效果有限。
where检索用不到的字段
7. 索引区分度
是指索引中某一列的唯一值数量与该列总记录数的比率。它反映了索引的有效性和查询性能。高区分度意味着索引能够更有效地缩小搜索范围,从而提高查询效率。
区分度可以通过以下公式计算:区分度= 总记录数 / 唯一值的数量
高区分度:当区分度接近1时,表示该列的值非常独特,索引效率高。适合用于查找、排序和连接操作。