java多线程同步需分层选用工具:原子类适用于单变量简单操作,如计数器、状态标志、无锁结构;锁用于多变量协同、复合逻辑及强一致性场景;二者常混合使用,如原子类主干+锁兜底、锁内嵌原子操作、分段加锁+原子局部计数。

Java 多线程同步问题不能靠单一机制“一招鲜”,而要根据操作粒度、协作复杂度和性能要求分层应对。锁(synchronized/ReentrantLock)和原子类(如 AtomicInteger、AtomicReference)不是互斥选项,而是互补工具——前者管“一段逻辑”的整体安全,后者管“一个变量”的高效更新。
什么时候优先用原子类
适用于单变量的简单读-改-写操作,且无需与其他变量联动:
-
计数器类场景:比如请求统计、点击量、限流令牌数。用
AtomicInteger.incrementAndGet()替代i++,避免丢失更新; -
状态标志切换:如
AtomicBoolean.compareAndSet(true, false)实现一次性状态变更(如任务完成标记),比加锁更轻量; -
无锁栈/队列结构:基于
AtomicReference实现 Treiber Stack 等并发数据结构,规避锁开销。
注意:原子类不解决“检查后执行”(check-then-act)问题。例如 if (counter.get() > 100) counter.set(0) 仍存在竞态——中间可能被其他线程修改,此时必须升级为锁或使用 AtomicInteger.accumulateAndGet() 等带函数式语义的方法。
什么时候必须上锁
当操作涉及多个变量、跨步骤逻辑或需要强一致性保障时,原子类无法覆盖,必须用锁:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
多变量协同更新:银行转账(A账户扣款 + B账户入账),两个账户余额必须同时成功或同时失败,
AtomicInteger只能保单个变量,无法保证整体事务性; -
复合判断+修改:如“若缓存未命中则加载并写入”,
ConcurrentHashMap.computeIfAbsent()内部已加锁,但自定义逻辑(如先查 DB 再存缓存)需显式同步; - 临界资源非原子访问:操作对象本身不可分割(如 ArrayList 的 add + size 判断)、或需保证执行顺序(如生产者-消费者中的 wait/notify 协作)。
锁对象要一致:用 synchronized(this) 仅对同一实例有效;共享静态状态必须用 synchronized(Counter.class) 或静态锁对象。
混合使用的典型模式
真实系统中常将二者组合,兼顾性能与正确性:
-
原子类做主干,锁兜底:用
AtomicLong维护高频更新的指标(如 QPS),另起一个低频线程定期用synchronized块汇总到监控系统,避免每次上报都锁; -
锁内嵌原子操作:在
synchronized代码块中调用AtomicInteger.getAndIncrement()——虽然冗余,但逻辑清晰,且不破坏原有同步边界; -
分段加锁 + 原子局部计数:如高并发 ID 生成器,按号段分片(每片用 ReentrantLock),片内用
AtomicLong快速分配,既减少锁争用,又保证片内连续性。
避坑要点
几个容易忽略但关键的细节:
-
volatile不是锁,也不替代原子类:它只保可见性和有序性,i++即使修饰为volatile int i依然线程不安全; -
ConcurrentHashMap.size()返回估算值,不能用于条件判断;需判空用.isEmpty(),需精确计数应单独维护AtomicLong; - ReentrantLock 要配
finally unlock(),而synchronized由 JVM 自动释放,不易出错; - 高争用下 CAS 失败率高(如 100 个线程抢同一个
AtomicInteger),CPU 空转明显,此时直接上锁反而更稳。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










