synchronized 和 ReentrantLock 的区别

77 阅读2分钟

一句话结论

  • synchronized:语法级、JVM 内建监视器锁,简单、自动释放,功能少但足够稳。

  • ReentrantLock:基于 AQS 的可重入显式锁,可中断/可超时/可选公平/多条件队列,功能强更灵活,但用法更复杂、需手动解锁。

共同点

  • 都是可重入的互斥锁。

  • 都提供happens-before 语义:解锁/退出同步块前的写,对随后同一锁上的加锁/进入同步块可见。

  • 都可用于保护临界区,避免数据竞争。

主要差异(速览表)

维度synchronizedReentrantLock
实现JVM 监视器(enter/exit monitor)AQS(队列 + CAS 计数)
获取/释放进入/退出代码块自动完成lock() / unlock()(需 try/finally)
可中断获取不支持lockInterruptibly() 支持
超时/尝试获取不支持tryLock() / tryLock(timeout)
公平性不支持构造可选公平/非公平
条件队列wait/notify/notifyAll(一个等待集)newCondition() 可建多个条件队列
调试与状态无法查询持有者/重入次数isHeldByCurrentThread() / getHoldCount() 等
性能取向低/中竞争下非常优化,语法最简高竞争/复杂条件下更可控(但要小心误用)
易用性/出错面简单、自动释放,不易漏锁灵活但易漏 unlock()、易用错条件队列

代码对照

synchronized(最简)

private final Object lock = new Object();

void put(X x) {
  synchronized (lock) {
    // 临界区
  } // 自动释放
}

条件等待(只有一个等待集):

synchronized (lock) {
  while (!ready) lock.wait();  // 可能虚假唤醒,必须 while 检查
  // ...
}
// 另处:
synchronized (lock) { 
  ready = true; 
  lock.notifyAll(); 
}

ReentrantLock(更灵活)

import java.util.concurrent.locks.*;

private final ReentrantLock lock = new ReentrantLock(); // 可 new ReentrantLock(true) 公平

void put(X x) {
  lock.lock();
  try {
    // 临界区
  } finally {
    lock.unlock();  // 必须在 finally 释放
  }
}

可中断/超时/多条件:

if (lock.tryLock(50, TimeUnit.MILLISECONDS)) { // 超时获取
  try { /* ... */ } finally { lock.unlock(); }
}

lock.lockInterruptibly(); // 可响应中断

Condition notEmpty = lock.newCondition();
lock.lock();
try {
  while (!hasData) notEmpty.await(); // await/signal 多队列可分离条件
  // ...
} finally {
  lock.unlock();
}

选型建议

  • 首选 synchronized:简单同步、低~中等竞争、无需可中断/超时/公平/多条件,大多数业务代码够用且可读性好。

  • 选 ReentrantLock,当你需要:

    • 可中断(避免线程无法被取消)或超时获取(防止长时间阻塞)。
    • 公平策略(减轻饥饿)。
    • 多个条件队列(不同条件分流,避免“广播唤醒”)。
    • 更丰富的状态查询/诊断(定位死锁/卡顿)。
  • Android 提醒:两者都是阻塞式。不要在主线程长时间 lock();如要在协程中用,请放到 Dispatchers.IO,或优先用挂起友好的 Mutex。

常见坑

  1. ReentrantLock 漏解锁:必须 try/finally 包裹。
  2. 条件等待误用:await()/wait() 必须放在持锁区,且用 while 重新校验条件。
  3. 滥用公平锁:公平性降低吞吐,仅在确实有饥饿时启用。
  4. 把锁当读写锁用:需要读多写少并发,请考虑 ReentrantReadWriteLock 或 StampedLock(后者不可重入、适合乐观读)。