AQS源码分析之ReentrantLock

本文深入剖析了Java ReentrantLock的实现机制,通过AQS框架,讲解了其公平/非公平锁、可重入、中断、状态管理和加解锁过程。重点比较了与synchronized的差异,以及ReentrantLock的构造方法和tryLock方法的灵活性。

AQS是jdk并发包java.util.concurrent下绝大部分工具类实现的基础,比如条件队列,阻塞队列,独占锁,共享锁等。
AQS具备特性
● 独占/共享
● 可重入
● 允许中断
● 公平/非公平
● 阻塞等待
ReentrantLock使用方法非常简单 只需要在需要加锁的逻辑前调用lock() unlock() 即可进行加减锁。

ReentrantLock lock = new ReentrantLock();

new Thread(() -> {
  lock.lock(); // 加锁
  try {
		todo();
  }catch (Exception e) {
		log.error();
  }finally {
  	lock.unlock(); // 解锁
  }

}).start();

new Thread(() -> {
  lock.lock();
  try {
		todo();
  }catch (Exception e) {
		log.error();
  }finally {
  	lock.unlock(); // 解锁
  }

}).start();

ReentrantLock对比synchronized
● synchronized是JVM层面实现的,ReentrantLock是JDK层面的锁。
● synchronized只有非公平,ReentrantLock有公平/非公平两种。
● synchronized不可被中断,ReentrantLock#lockInterruptibly()是可中断的。
● synchronized不可获取加锁状态,ReentrantLock#isLocked()是可以获取锁状态的。
● 发生异常时synchronized会自动释放锁,ReentrantLock需通过finally来显示释放。
● synchronized加锁方式单一,ReentrantLock可通过tryLock()快速获取锁加锁结构,以及设置可等待时长等更为灵活。

既然ReentrantLock这么强大 那我们来看看他是怎么实现的吧
看源码呢 带着问题去读 会更有效果
在这里插入图片描述
ReentrantLock构造方法有两种,通过构造方法我们可以获取公平/非公平锁,默认为非公平。在这里插入图片描述
在这里插入图片描述
AbstractQueuedSynchronizer(简称AQS)实现的,AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。公平锁与非公平锁都是基于AQS实现的。
在这里插入图片描述
图1
图1
加锁时首先会调用一个acquire()
通过tryAcquire()我们会找到父类AQS的实现方法nonfairTryAcquire()
图2
首先会获取我们的线程与state。
这个state是我们需要关注的点。它也是可ReentrantLock实现可重入、共享锁的关键。
在这里插入图片描述
state初始化为0 所以上边的逻辑我们只需要先关注 == 0的逻辑即可
-》图2。compareAndSetState即为我们常见的CAS逻辑。CAS可以通过JVM层面来保证我们操作的原子性。这个方法可以把acquires赋值给state并返回boolean,即如果state当前状态为0(没有被其他线程所修改的情况)则当前线程可将其更改为1。
此时我们假设当前线程为thread1且修改state状态成功。
在这里插入图片描述
则可通过setExclusiveOwnerThread()设置当前锁拥有者为此线程。
-》图2。所示此时thread1 获取锁并返回true。持有锁并加锁成功。
如果thread1再次进入另一个加锁逻辑会怎样呢。此时state会进行+1来记录thread1 进行了两次加锁。通过state
的状态我们可以看出state与ReentrantLock的可重入特性紧密相关。

-》图2。如果此时thread2进入加锁逻辑,由于之前thread1先对state进行了状态的更改。此时只能返回false。
-》图1。此时会执行addWaiter(Node.EXCLUSIVE)方法
在看后边逻辑之前,我们先了解下AQS
AQS有两种资源模式
● Exclusive-独占,只有一个线程能执行,如ReentrantLock
● Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
基于MESA管程模型AQS还有两种队列
● 同步等待队列:主要用于维护获取锁失败的线程。同步队列为双向链表。
● 条件等待队列:用于维护调研await()释放锁后的线程,当调用signal()唤醒后会把条件队列的线程节点全部移动到同步队列中,等待再次获取锁。条件队列为单向链表。
此Node为AQS实现的同步队列

static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
    	// 共享
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
    	// 独占
        static final Node EXCLUSIVE = null;

        static final int CANCELLED =  1;
  
        static final int SIGNAL    = -1;

        static final int CONDITION = -2;
   
        static final int PROPAGATE = -3;


        volatile int waitStatus;


        volatile Node prev;

 
        volatile Node next;

   
        volatile Thread thread;
}

创建节点后 会通过循环来初始化同步等待队列,并使thread2入队返回当前node节点
此时生成的队列状态为
在这里插入图片描述
之后会调用acquireQueued()首先进行判断当前thread2节点的prev是否为Head节点。如果是Head节点,则会再次进行获取锁逻辑。因为此时刚入队的thread2还未被park。可以再次尝试来降低线程切换带来的性能损耗。
图3
图3
此时如果获取锁成功则会把头节点制空,然后当前thread2节点变为新的Head节点
在这里插入图片描述
如果加锁失败则shouldParkAfterFailedAcquire(p, node)使Head节点的waitStatus变为-1,表示后边的下一个节点为可唤醒状态。并通过parkAndCheckInterrupt()对thread2进行park。

至此加锁逻辑分析完毕。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值