结合ReentrantLock了解AQS

56 阅读3分钟

ReentrantLock是Java中比较常用的锁之一,它的实现基于AQS(AbstractQueuedSynchronizer)抽象框架。在使用ReentrantLock时,我们可以通过封装AQS来实现自定义的同步器,满足特定的线程协作需求。

下面,我将结合ReentrantLock,介绍一下AQS的基本原理和实现方法。

  1. AQS的基本原理

在Java中,AQS是一种用来构建锁和同步器的抽象框架。它提供了一个用于管理等待线程的FIFO双向链表,同时提供了一些原子操作方法,能够方便地支持加锁、释放锁以及线程挂起等功能。

在AQS的内部实现中,有两个状态变量:state(表示可用资源数量)和exclusiveOwnerThread(表示当前拥有锁的线程)。其中,state通常表示锁的可用性,其值可以是任意的正整数;exclusiveOwnerThread则表示当前锁被哪个线程拥有,其值可以是null或具体的线程对象。

当多个线程竞争锁时,会同时尝试去修改state和exclusiveOwnerThread这两个状态变量。如果修改成功,则表示当前线程成功获取到了锁,否则需要将线程加入等待队列中,并挂起线程,直至被唤醒后再次尝试获取锁。

  1. ReentrantLock与AQS的关系

在ReentrantLock的实现中,它的内部类Sync继承了AQS,并在其中实现了tryAcquire和tryRelease方法。其中,tryAcquire方法尝试去获取锁,如果当前锁没有被其他线程占用,则获取锁成功,并将拥有者线程设置为当前线程;否则返回false,表示获取锁失败。而tryRelease方法则负责释放锁。

此外,在ReentrantLock中,还实现了Condition类,它在AQS的基础上提供了线程间的等待和唤醒机制。使用Condition,我们可以实现比synchronized更灵活的线程协作方案。

  1. AQS的实现方法

在AQS的实现中,主要包含以下方法:

  • getState()/setState(int state):获取/设置AQS的状态;
  • compareAndSetState(int expect, int update):比较当前状态是否等于expect,如果相等则将状态更新为update;
  • enqueue(Thread node):将当前线程加入等待队列;
  • dequeue(Thread node):将当前线程移出等待队列;
  • acquire(int arg):尝试获取锁,并在获取失败的情况下将当前线程加入到等待队列中;
  • acquireInterruptibly(int arg):在尝试获取锁时,如果线程被中断,则抛出InterruptedException异常;
  • tryAcquireNanos(int arg, long nanosTimeout):在指定时间内尝试获取锁,如果超时则返回false;
  • tryAcquire(int arg):尝试获取锁;
  • release(int arg):释放锁;
  • acquireShared(int arg):尝试获取共享锁;
  • acquireSharedInterruptibly(int arg):在尝试获取共享锁时,如果线程被中断,则抛出InterruptedException异常;
  • tryAcquireSharedNanos(int arg, long nanosTimeout):在指定时间内尝试获取共享锁,如果超时则返回false;
  • tryAcquireShared(int arg):尝试获取共享锁;
  • releaseShared(int arg):释放共享锁;
  • hasQueuedPredecessors():判断当前线程是否有等待者。

在AQS的实现中,尤其需要注意的是线程挂起和恢复的相关逻辑。它们通过waitStatus字段和Thread类的park/unpark方法实现。waitStatus字段保存当前节点的状态,它的取值为以下四种之一:CANCELLED(1)、SIGNAL(-1)、CONDITION(-2)和PROPAGATE(-3)。其中,CANCELLED表示该节点已被取消;SIGNAL表示该节点的后继节点需要被唤醒;CONDITION和PROPAGATE分别表示节点正在等待condition和共享状态的传播。

对于线程的挂起和恢复,AQS主要通过以下两个方法实现:

  • unparkSuccessor(Node node):唤醒node的后继节点;
  • parkAndCheckInterrupt():将当前线程挂起,并检测中断状态。

这两个方法的实现需要基于CAS(compareAndSwap)操作来保证线程操作的原子性。

以上就是AQS的基本原理和在ReentrantLock中的应用。需要注意的是,在实际使用中,我们通常会使用ReentrantLock或其他现有的同步器,而不是手动实现AQS。