volatile主要解决共享变量的可见性和指令重排序问题,通过内存屏障实现;不保证原子性,适用于状态标志位、不可变对象引用发布等场景,不可替代synchronized处理复合操作。

Java线程安全面试问题,核心是考察你对“共享状态 + 并发访问”下数据一致性和执行正确性的理解。不能只背代码,要能说清场景、问题本质、解决方案的适用边界和取舍逻辑。
volatile 关键字到底解决了什么?
volatile 主要解决两个问题:可见性、禁止指令重排序(通过内存屏障)。它不保证原子性——比如 i++ 这种读-改-写操作,即使 i 是 volatile 的,依然可能出错。常见误用是拿它替代 synchronized 处理计数器。真正适合的场景包括:状态标志位(如 running = false)、发布不可变对象引用、double/long 的 64 位原子读写(JVM 保证)。
synchronized 和 ReentrantLock 怎么选?
两者都提供互斥和内存可见性保障,但设计目标不同:
- synchronized 是 JVM 层原生支持,自动加锁解锁,简洁安全,适合大多数简单同步场景;
- ReentrantLock 提供更细粒度控制:可中断等待(lockInterruptibly)、超时获取(tryLock(timeout))、公平锁选择、多个 Condition 实例(如生产者/消费者中 notFull / notEmpty 分离);
- 注意:Lock 必须手动在 finally 块中 unlock,否则易造成死锁;synchronized 则无此风险。
集合类的线程安全怎么应对?
不是所有“线程安全集合”都适合同一场景:
立即学习“Java免费学习笔记(深入)”;
- Hashtable / Vector 是早期 synchronized 封装,粒度粗、性能低,已不推荐;
- Collections.synchronizedXxx() 只是对方法加 synchronized,遍历仍需手动同步(否则可能 ConcurrentModificationException);
- ConcurrentHashMap 是首选:JDK8 使用 CAS + synchronized 锁单个桶,支持高并发读写;注意 computeIfAbsent 等复合操作仍是原子的,但 size() 返回的是估算值;
- ArrayList 不安全,若需线程安全且频繁读,可用 CopyOnWriteArrayList(适用于读多写少、迭代多于修改的场景)。
为什么 HashMap 在多线程下会出问题?
关键看 JDK 版本:
- JDK7:扩容时头插法链表反转,多线程下易形成环形链表,get() 时无限循环;
- JDK8:改为尾插法,避免了环形链表,但仍不线程安全:put 操作可能丢失更新(CAS 失败后未重试)、size 不准确、遍历时可能报 ConcurrentModificationException;
- 结论:永远不要在多线程环境中直接使用 HashMap,该用 ConcurrentHashMap 就用,别试图“自己加锁保护”。


















