volatile不能保证原子性,++操作含读-改-写三步,多线程下仍会丢失更新;synchronized锁的是对象而非方法,不同实例无互斥;ThreadLocal不自动清理value易致内存泄漏;Executors.newFixedThreadPool()用无界队列,高负载易OOM。

volatile 不能保证原子性,别把它当锁用
很多人看到 volatile 能禁止指令重排、保证可见性,就以为给 count 加了 volatile 就能安全自增。实际不是:++ 操作本身是读-改-写三步,volatile 不拦着其他线程插在中间执行。结果就是两个线程同时读到 1,各自加成 2,最后写回还是 2,丢了一次更新。
常见错误现象:volatile int count = 0; 配合多线程 count++,最终值远小于预期。
- 正确做法:用
AtomicInteger的incrementAndGet(),或用synchronized块保护临界区 - 注意:
volatile适合“一写多读”且操作本身是原子的场景,比如状态标志位isRunning - 性能上,
volatile比锁轻量,但比普通变量重;别为了省事硬套在非原子操作上
synchronized 锁的是对象,不是代码块或方法名
写个 synchronized void methodA(),就以为所有调用它的线程都会互斥?错。锁的是当前实例(this),如果多个线程操作的是不同对象,那根本没锁住。静态方法锁的是类对象(MyClass.class),和实例锁完全不相干。
常见错误现象:启动多个 new Task() 实例,每个都调用 synchronized 方法更新共享资源,结果还是并发冲突。
立即学习“Java免费学习笔记(深入)”;
- 确认锁目标:想保护全局资源,优先用类锁(
synchronized(MyClass.class))或显式静态锁对象 - 避免锁字符串字面量(如
synchronized("key")),容易因字符串常量池被意外共享 - 锁粒度要小心:锁整个方法可能过度,改成只锁关键段能提升吞吐
ThreadLocal 不是万能的“线程私有存储”,用完不清理会内存泄漏
ThreadLocal 确实让每个线程拿到自己的副本,但底层是靠线程内部的 ThreadLocalMap 存值,而这个 map 的 key 是 ThreadLocal 的弱引用——value 却是强引用。一旦 ThreadLocal 实例被回收(比如定义为局部变量),key 变 null,但 value 还挂着,GC 清不掉,尤其在线程池里反复复用线程时,越积越多。
常见错误现象:Web 应用跑几天后 OOM,堆里堆满 ThreadLocalMap$Entry,value 是大对象(比如用户上下文、数据库连接)。
- 务必在业务结束时调用
threadLocal.remove(),别依赖set(null) - 不要把
ThreadLocal当成“自动清理”的容器;它只是延迟绑定,不自动释放 - 线程池场景下,建议在任务执行前后统一做
remove(),或用 try-finally 包裹
Executors.newFixedThreadPool() 在高负载下可能直接 OOM
这个工厂方法背后用的是无界队列 LinkedBlockingQueue,意味着只要任务提交速度 > 执行速度,队列就无限增长。一旦突发流量打进来,大量任务排队,堆内存很快撑爆。
常见错误现象:系统在压测或高峰时突然 OutOfMemoryError: Java heap space,jstack 显示大量线程阻塞在 take(),堆 dump 里全是待执行的 Runnable。
- 生产环境禁用
Executors.newFixedThreadPool()和newCachedThreadPool() - 改用
ThreadPoolExecutor构造器,明确指定有界队列(如ArrayBlockingQueue)和拒绝策略(如AbortPolicy或自定义降级逻辑) - 队列大小不能拍脑袋定:结合平均任务耗时、QPS、可接受排队时长来估算,宁小勿大










