Java多线程处理_Java多线程数据处理与同步技巧

多线程共享变量未加锁会导致脏读、丢失更新等数据不一致问题,根本原因是JVM内存模型下线程缓存副本不一致;应依场景选用AtomicInteger、synchronized、ConcurrentHashMap等线程安全方案,并注意ThreadLocal泄漏、CompletableFuture异常吞没及线程池拒绝策略配置。

多线程共享变量没加锁,结果总对不上

Java里多个线程同时读写同一个intArrayList或自定义对象字段时,不加同步机制,大概率出现脏读、丢失更新。这不是“偶尔出错”,而是JVM内存模型决定的:线程可能

一直用自己缓存的副本,根本看不到别的线程改过的值。

实操建议:

  • 简单计数优先用AtomicInteger,它靠CAS指令保证原子性,比synchronized轻量;
  • 逻辑复杂(比如先查再改)必须用synchronized块或ReentrantLock,锁对象要明确——别用this或临时对象,推荐用私有final对象:private final Object lock = new Object();
  • ArrayListHashMap这些非线程安全集合,别想着“我只读不写就没事”——迭代过程中另一个线程修改结构,会直接抛ConcurrentModificationException
  • 真要共享集合,用CopyOnWriteArrayList(适合读多写少)、ConcurrentHashMap(高并发场景),但注意它们的弱一致性:size()可能不是实时准确值。

用ExecutorService提交任务,线程池拒绝策略怎么选

直接new Thread().start()在高并发下会OOM,必须用线程池。但队列满了、核心线程全忙时,新任务怎么处理?默认的AbortPolicy直接抛RejectedExecutionException,线上服务扛不住。

实操建议:

  • 吞吐优先且允许丢弃:用DiscardPolicy(静默丢弃)或DiscardOldestPolicy(丢最老的,给新任务腾位置);
  • 业务不允许丢任务:自定义RejectedExecutionHandler,把任务转存到Kafka或数据库,后续重试;
  • 别盲目调大LinkedBlockingQueue容量——无界队列会让内存持续增长,最终OOM;
  • 监控关键指标:线程池getActiveCount()getQueue().size()getCompletedTaskCount(),异常飙升说明配置不合理或下游慢。

ThreadLocal用完不remove,导致内存泄漏

ThreadLocal本意是让每个线程独享变量副本,但它的底层实现是把值存在当前线程的ThreadLocalMap里,而这个map的key是ThreadLocal的弱引用。如果线程长期存活(比如线程池里的线程),value却一直强引用着,GC无法回收value,就会泄漏。

实操建议:

  • 每次用完必须显式调用threadLocal.remove(),尤其在try-finally里;
  • 不要把大对象(如ConnectionInputStream)塞进ThreadLocal
  • Web应用中,过滤器或拦截器里设了ThreadLocal,务必在响应结束前清理——Spring的RequestContextHolder.resetRequestAttributes()就是干这事的;
  • 排查泄漏:用MAT分析堆dump,搜ThreadLocalMap下的value,看是不是持有不该持有的类实例。

CompletableFuture异步链里异常被吞掉

supplyAsync().thenApply().thenAccept()时,如果中间某个阶段抛了未检查异常(比如NullPointerException),默认不会打印堆栈,也不会中断流程——除非你显式调用join()get(),否则异常就静默消失了。

实操建议:

  • 所有thenApplythenAccept之后,加上exceptionally()handle()捕获异常;
  • 不要在thenRun()里做耗时操作,它运行在ForkJoinPool.commonPool(),阻塞会导致整个公共池卡死;
  • 需要指定线程池执行后续阶段,用thenApplyAsync(fn, executor),别依赖默认池;
  • 组合多个CompletableFuture时,用allOf()anyOf()后,记得对每个子future单独join()get(),否则拿不到具体哪个失败了。

多线程最难的不是语法,是判断哪段代码真正需要同步、哪个对象该当锁、线程生命周期和业务生命周期是否对齐。很多问题在线上压测才暴露,因为低并发时竞争不激烈,掩盖了数据不一致或资源泄漏。