干货,深入剖析ReentrantLock源码,推荐收藏(reentrentlock原理)

  本篇文章为你整理了干货,深入剖析ReentrantLock源码,推荐收藏(reentrentlock原理)的详细内容,包含有reentrantlock.trylock reentrentlock原理 reentrantlock + synchronized reentrantlockcondition 干货,深入剖析ReentrantLock源码,推荐收藏,希望能帮助你了解 干货,深入剖析ReentrantLock源码,推荐收藏。

   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。

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

  以上就是干货,深入剖析ReentrantLock源码,推荐收藏(reentrentlock原理)的详细内容,想要了解更多 干货,深入剖析ReentrantLock源码,推荐收藏的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: