Java 并发之 ReentrantLock:实现逻辑、公平性及与 synchronized 的异同

摘要:在Java并发编程中,ReentrantLocksynchronized都是实现线程同步的核心机制,用于解决多线程竞争共享资源的问题。但它们的实现原理和使用方式有显著区别,尤其ReentrantLock提供了更灵活的同步控制能力。

一、ReentrantLock的实现原理

ReentrantLock是JDK提供的显式锁(需要手动获取和释放),其核心实现依赖于AQS(AbstractQueuedSynchronizer,抽象队列同步器)。简单来说,AQS是一个"模板类",定义了一套多线程访问共享资源的同步框架,ReentrantLock通过继承AQS并实现其抽象方法来完成锁的逻辑。

核心原理拆解:
  1. AQS的核心组件

    • 状态变量(state):一个int类型的变量,用于表示锁的状态。ReentrantLock是"可重入锁",所以state记录当前线程获取锁的重入次数(初始为0,获取锁后变为1,再次获取则递增,释放时递减,直到0表示锁被完全释放)。
    • 等待队列:当多个线程竞争锁失败时,会被封装成节点(Node)加入一个双向链表队列,等待被唤醒(类似"排队等待")。
    • 当前持有锁的线程(exclusiveOwnerThread):记录当前获取到锁的线程,用于实现"可重入"和"独占性"。
  2. 锁的获取与释放流程

    • 获取锁(lock())
      1. 线程尝试通过CAS操作修改state(从0→1),如果成功,标记当前线程为锁的持有者。
      2. 如果失败(说明锁已被占用),检查占用锁的线程是否是自己(可重入判断):如果是,state递增(重入次数+1);如果不是,当前线程进入等待队列阻塞。
    • 释放锁(unlock())
      1. 线程调用unlock()时,state递减(重入次数-1)。
      2. state减为0时,标记锁的持有者为null,并从等待队列中唤醒一个线程来竞争锁。
  3. 公平锁与非公平锁

    • ReentrantLock默认是非公平锁(构造函数可传入true创建公平锁)。
    • 非公平锁:新线程尝试获取锁时,可能"插队"(直接竞争锁,而不是排队),效率更高但可能导致线程饥饿。
    • 公平锁:新线程必须加入等待队列,按"先来后到"的顺序获取锁,公平但效率较低。

二、ReentrantLock与synchronized的区别

synchronized是Java内置的隐式锁(无需手动释放),依赖JVM实现;而ReentrantLock是JDK提供的工具类,依赖代码实现。两者核心区别如下:

维度 ReentrantLock synchronized
锁的获取/释放 显式操作:需手动调用lock()获取,unlock()释放(必须在finally中释放,否则可能死锁) 隐式操作:进入同步块/方法时自动获取,退出时自动释放(无需手动处理)
可重入性 支持(同一线程可多次获取锁,state记录重入次数) 支持(同一线程可多次进入同步块)
公平性 可选择公平/非公平锁(构造函数参数控制) 仅支持非公平锁(无法保证线程获取锁的顺序)
中断响应 支持中断等待锁的线程(lockInterruptibly()方法) 不支持:等待锁的线程会一直阻塞,无法被中断
超时获取 支持超时获取锁(tryLock(long timeout, TimeUnit unit)),超时后可放弃等待 不支持:获取不到锁会一直阻塞
条件变量(Condition) 支持多组条件变量(通过newCondition()创建),可精确唤醒特定线程 仅支持一组隐式条件变量(通过wait()/notify()/notifyAll()操作),唤醒时随机唤醒一个或全部
性能 高并发场景下性能更稳定(JDK 5后优化明显) JDK 6后引入偏向锁、轻量级锁等优化,低并发场景性能优异,高并发下略逊于ReentrantLock
使用复杂度 较高(需手动释放,否则易死锁) 较低(无需手动管理,更安全)

三、总结

  • synchronized:适合简单的同步场景,使用方便、不易出错,是Java官方推荐的首选方案(尤其JDK 6后的优化使其性能大幅提升)。
  • ReentrantLock:适合需要更灵活控制的场景(如公平锁、超时中断、多条件变量等),但需注意手动释放锁,否则可能导致死锁。

作为初学者,建议先掌握synchronized的使用,理解其基本原理后,再深入学习ReentrantLock和AQS的实现,从而更好地理解Java并发机制的设计思想。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐