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

多线程共享变量没加锁,结果总对不上
Java里多个线程同时读写同一个int、ArrayList或自定义对象字段时,不加同步机制,大概率出现脏读、丢失更新。这不是“偶尔出错”,而是JVM内存模型决定的:线程可能一直用自己缓存的副本,根本看不到别的线程改过的值。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 简单计数优先用
AtomicInteger,它靠CAS指令保证原子性,比synchronized轻量; - 逻辑复杂(比如先查再改)必须用
synchronized块或ReentrantLock,锁对象要明确——别用this或临时对象,推荐用私有final对象:private final Object lock = new Object();; -
ArrayList、HashMap这些非线程安全集合,别想着“我只读不写就没事”——迭代过程中另一个线程修改结构,会直接抛ConcurrentModificationException; - 真要共享集合,用
CopyOnWriteArrayList(适合读多写少)、ConcurrentHashMap(高并发场景),但注意它们的弱一致性:size()可能不是实时准确值。
用ExecutorService提交任务,线程池拒绝策略怎么选
直接new Thread().start()在高并发下会OOM,必须用线程池。但队列满了、核心线程全忙时,新任务怎么处理?默认的AbortPolicy直接抛RejectedExecutionException,线上服务扛不住。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 吞吐优先且允许丢弃:用
DiscardPolicy(静默丢弃)或DiscardOldestPolicy(丢最老的,给新任务腾位置); - 业务不允许丢任务:自定义
RejectedExecutionHandler,把任务转存到Kafka或数据库,后续重试; - 别盲目调大
LinkedBlockingQueue容量——无界队列会让内存持续增长,最终OOM; - 监控关键指标:线程池
getActiveCount()、getQueue().size()、getCompletedTaskCount(),异常飙升说明配置不合理或下游慢。
ThreadLocal用完不remove,导致内存泄漏
ThreadLocal本意是让每个线程独享变量副本,但它的底层实现是把值存在当前线程的ThreadLocalMap里,而这个map的key是ThreadLocal的弱引用。如果线程长期存活(比如线程池里的线程),value却一直强引用着,GC无法回收value,就会泄漏。
每个应用程序都要使用数据,Android应用程序也不例外,Android使用开源的、与操作系统无关的SQL数据库--SQLite,本文介绍的就是如何为你的Android应用程序创建和操作SQLite数据库。 数据库支持每个应用程序无论大小的生命线,除非你的应用程序只处理简单的数据,那么就需要一个数据库系统存储你的结构化数据,Android使用SQLite数据库,它是一个开源的、支持多操作系统的SQL数据库,在许多领域广泛使用,如Mozilla FireFox就是使用SQLite来存储配置数据的,iPhon
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 每次用完必须显式调用
threadLocal.remove(),尤其在try-finally里; - 不要把大对象(如
Connection、InputStream)塞进ThreadLocal; - Web应用中,过滤器或拦截器里设了
ThreadLocal,务必在响应结束前清理——Spring的RequestContextHolder.resetRequestAttributes()就是干这事的; - 排查泄漏:用MAT分析堆dump,搜
ThreadLocalMap下的value,看是不是持有不该持有的类实例。
CompletableFuture异步链里异常被吞掉
写supplyAsync().thenApply().thenAccept()时,如果中间某个阶段抛了未检查异常(比如NullPointerException),默认不会打印堆栈,也不会中断流程——除非你显式调用join()或get(),否则异常就静默消失了。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 所有
thenApply、thenAccept之后,加上exceptionally()或handle()捕获异常; - 不要在
thenRun()里做耗时操作,它运行在ForkJoinPool.commonPool(),阻塞会导致整个公共池卡死; - 需要指定线程池执行后续阶段,用
thenApplyAsync(fn, executor),别依赖默认池; - 组合多个
CompletableFuture时,用allOf()或anyOf()后,记得对每个子future单独join()或get(),否则拿不到具体哪个失败了。
多线程最难的不是语法,是判断哪段代码真正需要同步、哪个对象该当锁、线程生命周期和业务生命周期是否对齐。很多问题在线上压测才暴露,因为低并发时竞争不激烈,掩盖了数据不一致或资源泄漏。









