尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java多线程并发,这7个坑踩一个就翻车

Java多线程并发,这7个坑踩一个就翻车 多线程是Java进阶的必修课但也是最容易翻车的区域。很多Bug在单线程测试下毫无征兆一上生产就偶发、难复现、破坏力极强。下面这7个坑踩中任何一个都可能让系统在流量高峰时突然崩溃。坑一用Executors创建线程池Executors.newFixedThreadPool和newSingleThreadExecutor使用无界队列任务堆积时可能耗尽内存导致OOMnewCachedThreadPool最大线程数为Integer.MAX_VALUE高并发下会创建海量线程直接拖垮系统。正确做法是手动new ThreadPoolExecutor明确核心线程数、最大线程数、队列容量和拒绝策略让线程池行为可控。坑二共享可变变量缺少同步多个线程读写同一个变量时如果没有同步会出现可见性、原子性和有序性问题。比如i看似一行实际是读-改-写三步多线程下必然丢更新。解决方案用synchronized、Lock或AtomicInteger等原子类。注意volatile只保证可见性和有序性不保证原子性不能替代锁。坑三死锁两个线程互相持有对方需要的锁同时等待对方释放程序永久卡死。典型场景A线程锁住资源1请求资源2B线程锁住资源2请求资源1。避免方法统一加锁顺序使用tryLock带超时减少锁嵌套。一旦线上死锁只能重启代价极高。坑四双重检查锁定的单例忘加volatile经典DCL单例中instance new Singleton()不是原子操作可能发生指令重排分配内存、赋值引用、初始化对象。如果另一个线程在初始化完成前拿到非null引用就会访问到未初始化的对象。必须给instance加volatile禁止重排否则看似正确的代码暗藏杀机。坑五误用ConcurrentHashMap的复合操作ConcurrentHashMap的单个操作是线程安全的但“检查再执行”的复合操作不是。比如if (!map.containsKey(k)) map.put(k, v);两个线程可能同时通过检查导致重复写入。应使用putIfAbsent、computeIfAbsent等原子方法。同理Collections.synchronizedList的遍历仍需手动加锁。坑六ThreadLocal内存泄漏ThreadLocal的 key 是弱引用但 value 是强引用。线程池中的线程长期存活如果使用完不调用remove()value 会一直挂在 ThreadLocalMap 中导致内存泄漏。正确做法在finally中调用threadLocal.remove()尤其是 Web 容器和线程池场景。坑七SimpleDateFormat线程不安全SimpleDateFormat内部使用 Calendar 等可变状态多线程共享同一个实例时解析和格式化结果会错乱甚至抛出NumberFormatException。解决方案每个线程创建独立实例用ThreadLocal包装或直接使用 Java 8 的DateTimeFormatter它是线程安全的。结语多线程并发的坑远不止这7个但这7个是最常见、最容易在生产环境引爆的。它们的共同点是单线程测试正常高并发下偶发排查困难修复成本高。避免翻车的关键不是背下所有坑而是建立并发安全意识共享可变状态必须同步线程池必须手动配置复合操作必须原子化线程局部变量必须清理。下次写多线程代码时多问一句这里有没有竞态有没有可见性问题如果答案不确定宁可加锁也不要赌运气。
返回列表