首页IT科技lock和reentrantlock区别(干货,深入剖析ReentrantLock源码,推荐收藏)

lock和reentrantlock区别(干货,深入剖析ReentrantLock源码,推荐收藏)

时间2025-05-02 09:33:21分类IT科技浏览4911
导读:ReentrantLock和Synchronized都是Java开发中最常用的锁,与Synchronized这种JVM内置锁不同的是,ReentrantLock提供了更丰富的语义。可以创建公平锁或非公平锁、响应中断、超时等待、按条件唤醒等。在某些场景下,使用ReentrantLock更适合,功能更强大。...

ReentrantLock和Synchronized都是Java开发中最常用的锁            ,与Synchronized这种JVM内置锁不同的是                  ,ReentrantLock提供了更丰富的语义            。可以创建公平锁或非公平锁            、响应中断                  、超时等待      、按条件唤醒等                  。在某些场景下      ,使用ReentrantLock更适合         ,功能更强大      。

前两篇文章                  ,我们分析了AQS的加锁流程         、以及源码实现         。当时我们就说了         ,AQS使用了模板设计模式      ,父类中定义加锁流程                  ,子类去实现具体的加锁逻辑                  。所以大部分加锁代码已经在父类AQS中实现了            ,导致ReentrantLock的源码非常简单   ,一块学习一下         。

先看一下ReentrantLock怎么使用?

1. ReentrantLock的使用

/** * @author 一灯架构 * @apiNote ReentrantLock示例 **/ public class ReentrantLockDemo { public static void main(String[] args) { // 1. 创建ReentrantLock对象 ReentrantLock lock = new ReentrantLock(); // 2. 加锁 lock.lock(); try { // 3. 这里执行具体的业务逻辑 } finally { // 4. 释放锁 lock.unlock(); } } }

可以看到ReentrantLock的使用非常简单                  ,调用lock加锁               ,unlock释放锁,需要配置try/finally使用               ,保证在代码执行出错的时候也能释放锁      。

ReentrantLock也可以配合Condition条件使用                  ,具体可以翻一下前几篇文章中BlockingQueue的源码解析   ,那里面有ReentrantLock的实际使用                  。

再看一下ReentrantLock的类结构

2. ReentrantLock类结构

// 实现Lock接口 public class ReentrantLock implements Lock { // 只有一个Sync同步变量 private final Sync sync; // Sync继承自AQS            ,主要逻辑都在这里面 abstract static class Sync extends AbstractQueuedSynchronizer { } // Sync的两个子类                  ,分别实现了公平锁和非公平锁 static final class FairSync extends Sync { } static final class NonfairSync extends Sync { } }

可以看出ReentrantLock的类结构非常简单      ,实现了Lock接口            。

类里面有两个静态内部类         ,分别实现公平锁和非公平锁   。

看一下Lock接口中                  ,定义了哪些方法?

public interface Lock { // 加锁 void lock(); // 加可中断的锁 void lockInterruptibly() throws InterruptedException; // 尝试加锁 boolean tryLock(); // 一段时间内         ,尝试加锁 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 释放锁 void unlock(); // 新建条件状态 Condition newCondition(); }

就是一些使用锁的常用方法                  。

在上篇文章中浏览AQS源码的时候      ,了解到AQS定义了一些有关具体加锁                  、释放锁的抽象方法                  ,留给子类去实现            ,再看一下有哪些抽象方法:

// 加独占锁 protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } // 释放独占锁 protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); } // 加共享锁 protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); } // 释放共享锁 protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); } // 判断是否是当前线程正在持有锁 protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); }

由于ReentrantLock使用的是独占锁   ,所以只需要实现独占锁相关的方法就可以了               。

3. ReentrantLock源码解析

3.1 ReentrantLock构造方法

// 默认的构造方法                  ,使用非公平锁 public ReentrantLock() { sync = new NonfairSync(); } // 传true               ,可以指定使用公平锁 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }

在创建ReentrantLock对象的时候,可以指定使用公平锁还是非公平锁               ,默认使用非公平锁                  ,显然非公平锁的性能更好。

先思考一个面试常考问题   ,公平锁和非公平锁是怎么实现的?

3.2 非公平锁源码

先看一下加锁源码:

从父类ReentrantLock的加锁方法入口:

public class ReentrantLock implements Lock { // 加锁入口方法 public void lock() { // 调用Sync中加锁方法 sync.lock(); } }

在子类NonfairSync的加锁方法:

// 非公平锁 static final class NonfairSync extends Sync { // 加锁 final void lock() { // 1. 先尝试加锁(使用CAS设置state=1) if (compareAndSetState(0, 1)) // 2. 加锁成功            ,就把当前线程设置为持有锁线程 setExclusiveOwnerThread(Thread.currentThread()); else // 3. 没加锁成功                  ,再调用父类AQS中实际的加锁逻辑 acquire(1); } }

加锁逻辑也很简单      ,先尝试使用CAS加锁(也就是把state从0设置成1)         ,加锁成功                  ,就把当前线程设置为持有锁线程               。

设计者很聪明         ,在锁竞争不激烈的情况下      ,很大概率可以加锁成功                  ,也就不用走else中复杂的加锁逻辑了                  。

如果没有加锁成功            ,还是需要走else中调用父类AQS的acquire方法   ,而acquire又需要调用子类的tryAcquire方法   。

调用链路就是下面这样:

根据调用链路                  ,实际的加锁逻辑在Sync.nonfairTryAcquire方法里面            。

abstract static class Sync extends AbstractQueuedSynchronizer { // 非公平锁的最终加锁方法 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 1. 获取同步状态 int c = getState(); // 2. state=0表示无锁               ,先尝试加锁(使用CAS设置state=1) if (c == 0) { if (compareAndSetState(0, acquires)) { // 3. 加锁成功,就把当前线程设置为持有锁线程 setExclusiveOwnerThread(current); return true; } // 4. 如果当前线程已经持有锁               ,执行可重入的逻辑 } else if (current == getExclusiveOwnerThread()) { // 5. 加锁次数+acquires int nextc = c + acquires; // 6. 超过tnt类型最大值                  ,溢出了 if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }

再看一下释放锁的调用流程   ,公平锁和非公平锁流程是一样的            ,最终都是执行Sync.tryRelease方法:

abstract static class Sync extends AbstractQueuedSynchronizer { // 释放锁 protected final boolean tryRelease(int releases) { // 1. 同步状态减去释放锁次数 int c = getState() - releases; // 2. 校验当前线程不持有锁                  ,就报错 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 3. 判断同步状态是否等于0      ,无锁后         ,就删除持有锁的线程 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } }

再看一下公平锁的源码

3.3 公平锁源码

先看一下公平锁的加锁流程:

最终的加锁方法是FairSync.tryAcquire                  ,看一下具体逻辑:

static final class FairSync extends Sync { // 实现父类的加锁逻辑 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); // 1. 获取同步状态 int c = getState(); // 2. state=0表示无锁         ,先尝试加锁(使用CAS设置state=1) if (c == 0) { // 3. 判断当前线程是不是头节点的下一个节点(讲究先来后到) if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } // 4. 如果当前线程已经持有锁      ,执行可重入的逻辑 } else if (current == getExclusiveOwnerThread()) { // 5. 加锁次数+acquires int nextc = c + acquires; // 6. 超过tnt类型最大值                  ,溢出了 if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } // 判断当前线程是不是头节点的下一个节点(讲究先来后到) public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); } }

公平锁的释放锁逻辑跟非公平锁一样            ,上面已经讲过                  。

4. 总结

看完了ReentrantLock的所有源码   ,是不是觉得ReentrantLock很简单      。

由于加锁流程的编排工作已经在父类AQS中实现                  ,子类只需要实现具体的加锁逻辑即可         。

加锁逻辑也很简单               ,也就是修改同步状态state的值和持有锁的线程exclusiveOwnerThread                  。

我是「一灯架构」,如果本文对你有帮助               ,欢迎各位小伙伴点赞         、评论和关注                  ,感谢各位老铁   ,我们下期见

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
命令提示符 卸载程序(如何在Win11/10 中使用命令提示符卸载驱动程序) 文章不收录怎么办(文章不被收录的原因)