线程安全问题源于共享变量的非原子读写与内存可见性缺失,需用synchronized、无锁类(如ConcurrentHashMap)、volatile(仅限简单场景)等解决;死锁须按序加锁或超时重试;线程池应手动配置有界队列与合理拒绝策略;wait/notify必须用while循环重检条件并配notifyAll。

线程安全问题:共享变量被意外修改
多个线程同时读写同一个 ArrayList 或普通对象字段时,不加同步会导致数据错乱、丢失更新,甚至 ConcurrentModificationException。这不是偶发 bug,而是内存可见性与原子性缺失的必然结果。
- 用
synchronized块或方法保护临界区,但注意锁对象是否唯一(比如不要用this锁一个可变引用) - 优先考虑无锁方案:如用
ConcurrentHashMap替代HashMap,AtomicInteger替代int计数器 -
volatile只能保证可见性和禁止重排序,不能保证复合操作(如i++)的原子性
死锁:两个线程互相等待对方释放锁
典型场景是线程 A 持有锁 L1 并尝试获取 L2,同时线程 B 持有 L2 并尝试获取 L1。JVM 不会自动检测或中断,程序会永久挂起。
- 按固定顺序获取锁(例如总是先锁 id 小的对象),避免循环等待
- 使用
tryLock(long, TimeUnit)设定超时,失败后释放已持锁并重试或退避 - 避免在持有锁时调用外部方法(尤其是不可控的回调),防止隐藏锁依赖
线程池配置不当:OOM 或响应延迟飙升
Executors.newFixedThreadPool(10) 看似稳妥,但若任务阻塞(如等待数据库响应)、队列无界,会导致线程数不变但堆积任务耗尽堆内存;而 newCachedThreadPool() 在高并发下可能创建数千线程,触发系统级资源耗尽。
- 明确业务最大并发量和平均响应时间,用
ThreadPoolExecutor手动构造,设置有界队列(如ArrayBlockingQueue) - 拒绝策略别只用默认
AbortPolicy,根据场景选CallerRunsPolicy(让调用线程自己执行)或自定义降级逻辑 - 监控
getActiveCount()、getQueue().size(),而非仅看 CPU 使用率
虚假唤醒与条件等待失效:wait/notify 的经典陷阱
用 wait() 等待某个条件成立时,如果没用 while 循环重检条件,可能因虚假唤醒或信号丢失导致线程在条件不满足时继续执行,引发逻辑错误。
立即学习“Java免费学习笔记(深入)”;
- 永远用
while (condition) { obj.wait(); },而不是if -
notify()只唤醒一个线程,无法保证唤醒的是需要该信号的那个;多数场景应改用notifyAll() - 条件变量必须与同一把锁关联,且
wait()前必须已持有该锁
synchronized 底层怎么刷缓存、volatile 怎么插入内存屏障、ForkJoinPool 怎么偷任务——否则调参和排查都靠猜。











