Java实战面试题(二)

Java实战面试题(二) 目录1. Java中的锁有哪些2. 怎么保证 MySQL 中的原子性3. 缓存数据的一致性4. HashMap 的底层扩容机制线程安全吗5. Spring Boot 自动装配原理是什么6. 商品库存扣减怎么保证数据正确7. 线程池的线程工厂有了解吗8. Java中乐观锁和悲观锁的原理与区别1. Java中的锁有哪些回答思路从不同维度分类展现系统性理解。按实现方式分JVM内置锁synchronized可修饰方法、代码块锁升级机制偏向锁→轻量级锁→重量级锁。JUC显式锁ReentrantLock可重入公平/非公平可选支持条件变量 Condition、ReentrantReadWriteLock读共享写独占。按特性分乐观锁先操作再检查冲突。如 CASAtomicInteger、版本号机制数据库 version 字段。悲观锁假定冲突先加锁。如synchronized、ReentrantLock、数据库SELECT ... FOR UPDATE。可重入锁synchronized、ReentrantLock同一线程可多次获取。读写锁ReentrantReadWriteLock适合读多写少。自旋锁CAS 循环重试避免线程阻塞切换开销synchronized重量级锁前就会自旋。公平/非公平锁ReentrantLock构造函数可指定。分布式锁基于 RedisRedisson、Zookeeper、数据库实现。 面试答法Java 中的锁可以从多个维度分类。从实现上分内置锁 synchronized 和 JUC 下的显式锁如 ReentrantLock从思想上分乐观锁和悲观锁从功能上还有读写锁、可重入锁等。实际项目中本地锁如 ReentrantLock 适用于单服务跨服务场景则用 Redisson 分布式锁加 Lua 脚本保证原子性。2. 怎么保证 MySQL 中的原子性核心机制undo log回滚日志 事务机制。原子性定义事务中的所有操作要么全部成功要么全部失败回滚中途出错不能留下部分结果。实现原理事务开始后每次对数据的修改InnoDB 都会先将旧数据写入 undo log。如果事务执行中发生错误或用户主动ROLLBACKMySQL 会通过 undo log 中记录的旧值将数据逐条回滚到事务开始前的状态。如果COMMITundo log 会被标记为可清除。辅助机制redo log 配合保证事务提交后数据的持久性D与原子性配合构成 AID。 面试答法MySQL 的原子性主要依靠 undo log 实现。事务修改数据前先记录旧值到 undo log如果事务回滚就用 undo log 里的旧值恢复数据。redo log 更多是保证持久性两者共同保证 ACID 中的 A 和 D。3. 缓存数据的一致性见前文第 4 题详细解答。核心要点标准方案Cache Aside旁路缓存—— 先更新数据库再删除缓存。极端情况兜底延迟双删写完 DB删一次缓存延时几百毫秒再删一次。最终一致性方案Canal 监听 binlog投递 MQ异步更新缓存。确保底线缓存设置合理过期时间作为最终兜底。4. HashMap 的底层扩容机制线程安全吗底层结构JDK 1.8 之前数组 单向链表。JDK 1.8 及以后数组 链表 红黑树。当链表长度 ≥ 8 且数组长度 ≥ 64 时链表转为红黑树提升查询效率O(n) → O(log n)。扩容机制resize()触发条件当元素个数超过容量 × 负载因子默认0.75时。扩容动作创建一个原数组长度 2 倍的新数组。数据迁移遍历原数组每个位置桶重新计算每个元素在新数组中的位置。普通节点通过hash oldCap判断0 留原位1 去原位置 原数组长度。链表拆分为高低位两条链分别迁移。红黑树拆分为高低位两棵树若拆分后树节点数 ≤ 6退化为链表。非原子性风险扩容过程未加锁多线程并发可能导致数据丢失或形成环形链表CPU 100%。线程安全吗HashMap 是非线程安全的。替代方案ConcurrentHashMapJDK 1.8 使用CAS synchronized锁住桶的首节点只锁冲突桶并发度高。不锁整个表不允许存 null。Hashtable全表锁每次锁整个表并发低已过时。Collections.synchronizedMap(new HashMap())同步包装器性能也低。 面试答法HashMap 底层是数组加链表加红黑树链表过长会树化。扩容会创建一个两倍大小新数组然后通过高低位拆分迁移数据。它本身不是线程安全的多线程扩容可能产生死链。并发场景必须用 ConcurrentHashMap它对每个桶的头节点用 CAS 加 synchronized 实现分段锁。5. Spring Boot 自动装配原理是什么核心注解SpringBootApplication包含EnableAutoConfiguration。工作原理入口EnableAutoConfiguration通过Import(AutoConfigurationImportSelector.class)导入自动配置选择器。定位候选配置类AutoConfigurationImportSelector会使用SpringFactoriesLoader从 classpath 下所有META-INF/spring.factories文件中加载 key 为org.springframework.boot.autoconfigure.EnableAutoConfiguration的所有类名。条件过滤这些候选类如DataSourceAutoConfiguration上通常有ConditionalOnClass类路径存在指定类、ConditionalOnMissingBean容器中无此 Bean、ConditionalOnProperty配置文件有指定属性等条件注解。Spring Boot 会逐类评估这些条件。按需装配满足条件的类会被装载并创建其声明的 Bean 注入容器。不满足条件的类被忽略。自定义覆盖开发者自己创建的 Bean 会因ConditionalOnMissingBean而优先于自动配置生效。 面试答法Spring Boot 通过EnableAutoConfiguration扫描spring.factories文件中列出的所有自动配置类再根据Conditional系列注解按需装配。比如你引入了 MySQL 驱动它就会自动给你创建DataSourceBean但如果你自己配置了它就退让。6. 商品库存扣减怎么保证数据正确这是你售电/项目经验中的常见场景回答时要突出数据库层面的强一致性。方案一数据库行锁悲观锁—— 简单可靠SELECT stock FROM product WHERE id 1 FOR UPDATE; -- 加行锁在事务中先用FOR UPDATE锁住该行再判断stock 扣减值然后UPDATE stock stock - num。优点强一致性不会超卖实现简单。缺点高并发下大量请求串行等待性能瓶颈。优化可将扣减请求放入队列异步串行处理前端提示“排队中”。方案二乐观锁版本号/CAS—— 适合并发不那么极端UPDATE product SET stock stock - #{num}, version version 1 WHERE id #{id} AND stock #{num} AND version #{oldVersion};判断受影响行数0 表示冲突重试或提示。缺点并发极高时大量重试可能压死数据库。方案三Redis 分布式锁/原子扣减使用 Redis 的DECRBY原子操作预扣或 Redisson 加锁后扣减。但需处理缓存和 DB 的一致性问题。 面试答法核心是防止超卖。单服务下推荐直接用数据库行锁SELECT FOR UPDATE在事务中检查和扣减强一致性最高。如果并发更高可以引入 Redis 预减库存做挡板但一定要通过异步落库和最终一致性方案保证库存正确。乐观锁重试成本太高扣减场景不优先推荐。7. 线程池的线程工厂有了解吗定义ThreadFactory是一个接口用来创建线程。Executors.defaultThreadFactory()创建的线程默认优先级为NORM_PRIORITY非守护线程且有统一命名pool-x-thread-y。为什么需要自定义可识别的线程命名设成order-pool-thread-1方便排查堆栈信息、死锁和性能瓶颈。设置守护线程主线程结束时希望线程池也关闭可设setDaemon(true)。设置异常处理器通过setUncaughtExceptionHandler捕获线程内未处理异常做日志记录。示例ThreadFactory namedThreadFactory new ThreadFactoryBuilder() .setNameFormat(order-handler-%d) // 命名格式 .setDaemon(false) // 非守护 .build();Guava 的ThreadFactoryBuilder是其常用实现简洁可靠。 面试答法自定义线程工厂主要为了给线程起有业务含义的名字并可以统一设置异常处理器。推荐用 Guava 的ThreadFactoryBuilder代码简洁。实际开发中如果不用自定义名排查线上问题看到一堆pool-1-thread-1很难定位。8. Java中乐观锁和悲观锁的原理与区别维度悲观锁乐观锁思想认为并发操作大概率冲突每次操作都加锁认为冲突是小概率不加锁更新时检查数据是否被改实现synchronized,ReentrantLock, 数据库行锁SELECT ... FOR UPDATECASCompare And Swap、数据库版本号version字段适用场景写多读少竞争激烈锁持有时间短读多写少冲突较少优点强一致性不会出现更新丢失实现简单无锁操作不会死锁并发读性能高缺点锁竞争导致线程阻塞、上下文切换开销大易死锁冲突严重时循环重试耗 CPUCAS 有 ABA 问题只能保证一个共享变量的原子操作经典案例商品库存扣减防超卖用户积分更新、文档协同编辑 面试答法悲观锁假定冲突必然发生所以先拿锁再操作比如数据库行锁扣库存乐观锁假定冲突少更新时通过版本号或 CAS 检查是否被改过改了就不覆盖。实际应用看场景下单扣库存用悲观锁用户个人资料更新用乐观锁。