20260410

04月10日面试

1.手写一个线程安全的计数器(两种方法)

1)使用synchronized(最基础、必掌握

java
复制代码
public class Counter { private int count = 0; // 加一 public synchronized void increment() { count++; } // 获取值 public synchronized int getCount() { return count; } }

面试回答要点

  • 使用 synchronized 保证原子性、可见性、有序性
  • count++ 是三步操作(读→改→写),非原子,必须加锁
  • 简单可靠,但高并发下性能一般

2)使用 AtomicInteger(推荐,性能更好)

java
复制代码
import java.util.concurrent.atomic.AtomicInteger; public class Counter { private final AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } public int getCount() { return count.get(); } }

面试回答要点

  • 基于 CAS 无锁机制,性能比 synchronized 高
  • 利用 CPU 原语保证原子操作,避免线程阻塞
  • 适合高并发计数场景

2.手写线程安全的单例模式

1)双重校验锁(DCL)+ volatile(最常考)

java
复制代码
public class Singleton { // 禁止指令重排,保证多线程可见性 private static volatile Singleton instance; // 私有构造,禁止外部 new private Singleton() {} public static Singleton getInstance() { // 第一次判断,避免每次加锁 if (instance == null) { synchronized (Singleton.class) { // 第二次判断,防止多线程同时进入外层 if if (instance == null) { instance = new Singleton(); } } } return instance; } }

面试必说要点:

  • 构造器私有,防止外部实例化
  • volatile 禁止指令重排,避免 DCL 失效
  • 两次判空 + 锁类对象,保证线程安全
  • 懒加载,节约内存

2)静态内部类

java
复制代码
public class Singleton { private Singleton() {} // 静态内部类,只有被调用时才加载 private static class Holder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } }

优点:

  • JVM 类加载机制 保证线程安全
  • 懒加载、性能高、代码简洁
  • 无锁,面试加分项

3)饿汉式(最简单,但不是懒加载)

java
复制代码
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }

特点:

  • 天生线程安全,但类加载就初始化,可能浪费内存。

3.MySQL的ACID特性,InnoDB 如何保证

一、ACID 是什么

  • A(Atomicity)原子性:事务要么全部成功,要么全部失败回滚,不允许部分执行。
  • C(Consistency)一致性:事务执行前后,数据库完整性约束不变。
  • I(Isolation)隔离性:多个事务并发执行时,互相不可见、不干扰。
  • D(Durability)持久性:事务一旦提交,结果永久保存,宕机不丢失。

二、InnoDB 如何保证 ACID

  1. 原子性(A) 保证:undo log(回滚日志) 执行更新前,先把旧数据记入 undo log 事务失败时,通过 undo log 回滚到之前状态 配合 redo log 保证崩溃后仍能正确回滚
  2. 一致性(C) 保证:原子性 + 隔离性 + 持久性 + 约束检查 事务满足 AID 自然保证一致性 外加主键、外键、唯一约束、触发器等
  3. 隔离性(I) 保证:锁 + MVCC(多版本并发控制) 读写锁、间隙锁、临键锁解决脏写、幻读等问题 MVCC 实现读写不阻塞,通过 undo log 生成历史版本 配合四大隔离级别: Read Uncommitted Read Committed(RC) Repeatable Read(RR,InnoDB 默认) Serializable
  4. 持久性(D) 保证:redo log(重做日志) + 刷盘机制 事务提交前,先写 redo log(WAL 预写日志) 即使 buffer pool 数据未刷盘,宕机重启后也能通过 redo log 恢复 配合 binlog 保证主从一致

4.JVM内存模型,OOM的区域以及原因

一、JVM 内存区域划分(线程私有 + 线程共享)

  1. 线程私有区域(每个线程独立) 程序计数器 记录当前线程执行字节码行号。 虚拟机栈 存放局部变量、方法出口、栈帧。 本地方法栈 作用同虚拟机栈,为 native 方法服务。

  2. 线程共享区域(所有线程共用) 堆(Heap) 存放对象实例,GC 主要区域,分为新生代 + 老年代。 方法区(元空间 MetaSpace) 存储类信息、常量、静态变量、即时编译代码。 JDK8 后叫元空间,使用本地内存。 二、OOM 常见区域及原因(高频)

  3. 堆 OOM(Java heap space) 原因: 对象创建过多,且无法被 GC 回收(内存泄漏) 集合对象全局引用,一直持有不释放 堆内存设置过小(-Xmx 太小) 死循环不断创建对象 典型场景: 从数据库一次查询百万条数据加载进内存 静态 Map 无限缓存对象不清理

  4. 虚拟机栈 OOM / StackOverflowError StackOverflowError: 方法递归调用无出口,栈深度超限 OOM: 线程不断创建,栈内存耗尽

  5. 方法区 / 元空间 OOM(Metaspace) 原因: 大量动态生成类(CGLib、MyBatis、动态代理) 大量 JSP、反射类加载 常量池数据过多 元空间配置太小

  6. 直接内存 OOM 原因: NIO 框架频繁申请堆外内存 堆外内存未正确释放 总物理内存不足

5.ConcurrentHashMap

一、核心特点

  • 线程安全的高效哈希表,替代 HashTable、同步 HashMap
  • 读操作无锁,写操作分段锁 / CAS + synchronized,并发性能远高于 HashTable
  • 键值不能为 null

二、JDK 1.7 实现

  • 分段锁(Segment),继承自 ReentrantLock
  • 默认 16 个 Segment,并发度 16
  • 结构:Segment → 数组 + 链表
  • 锁粒度:锁住一个 Segment,多段可同时操作

三、JDK 1.8 实现(重点必问)

  • 取消 Segment,采用 CAS + synchronized 控制并发
  • 结构:数组 + 链表 + 红黑树
  • 锁粒度:只锁住链表 / 树的头节点,并发更高
  1. put 流程 根据 key 计算 hash 节点为空:CAS 插入 节点不为空:synchronized 锁住头节点 写入 链表长度 ≥ 8 且数组长度 ≥ 64 → 转为红黑树 扩容:达到负载因子 0.75 触发扩容
  2. get 流程 无锁,直接根据 hash 查找 利用 volatile 修饰数组 保证可见性

四、关键机制

  1. hash 计算

    高纬位参与运算,减少冲突

  2. 扩容机制

    扩容为原来 2 倍

    多线程协助迁移,提高效率

  3. 线程安全保证

    读:volatile 保证可见性

    写:CAS 无锁 + 轻量级锁 synchronized

五、对比总结

对比HashMapHashTableConcurrentHashMap
线程安全是(全表锁)是(细粒度锁)
性能高并发优秀
键值 null允许不允许不允许
JDK8 结构数组 + 链表 + 红黑树数组 + 链表数组 + 链表 + 红黑树
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
9716
下载 APP