目录
在AQS中实现了如何获取锁和释放锁的模板方法,重入锁ReentrantLock实现时通过内部类继承Sync同步器AbstractQueuedSynchronizer。并调用同步器提供的模板方法,而这些模板方法将会调用ReentrantLock重写的方法,这是典型的模板方法设计模式。AQS实现同步器功能离不开三大基础组件:
- 对共享资源同步状态进行原子性管理 ---> 利用CAS对同步状态进行更新
- 线程的阻塞与唤醒 ---> 调用native方法
- 等待队列的管理 ---> 维护FIFO队列
AQS同步状态
AQS中使用了一个int型的volatile变量来表示同步状态,线程在尝试获取锁的时候,就回去比较同步器同步状态state是否为0,为0,那么线程就拿到了锁并改变同步状态;不为0,说明有其他线程拿到了锁。AQS中提供了以下三个方法来访问或修改同步状态:
//AQS成员变量,同步状态 private volatile int state; //获取当前同步状态 protected final int getState() { return state; } //设置当前同步状态 protected final void setState(int newState) { state = newState; } //使用CAS设置当前状态,该方法能够保证状态设置的原子性 protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }AQS同步队列
当有多个线程竞争获取锁时,只有一个线程能获取到锁,那么这些没有获取到锁的线程就需要等待,等到线程把锁释放了再唤醒等待线程去获取锁,为了实现等待-唤醒机制,AQS提供了基于CLH队列(Craig, Landin,Hagersten)实现的等待队列,是一个先入先出的双向队列。同步队列是一个非阻塞的 FIFO 队列。也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和CAS保证节点插入和移除的原子性。

AQS中的内部类Node是构建同步队列和等待队列(后面介绍Condition再介绍)的基础节点类,Node类部分源码如下:
static final class Node { //等待状态 volatile int waitStatus; //前驱结点 volatile Node prev; //后继节点 volatile Node next; //等待获取锁的线程 volatile Thread thread; //condition队列的后继节点 Node nextWaiter; }关于节点Node的waitStatus,它反映的是节点中线程的等待状态,有如下取值:
- CANCELLED,值为1,因为超时或中断,该线程已经被取消
- SIGNAL,值为-1,线程的后继线程正/已被阻塞,当该线程release或cancel时要重新这个后继线程(unpark)
- CONDITION,值为-2,表明该线程被处于条件队列,就是因为调用了Condition.await而被阻塞
- PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行
- 等待状态的初始值为0,表示当前节点在sync队列中,等待着获取锁。
ReentrantLock数据结构
从关系图可以看出,ReentrantLock实现了Lock接口,内部类Sync是AQS的子类,Sync有两个子类FairSync(公平锁)和NonFairSync(非公平锁)。ReentrantLock只有一个成员变量sync,通过构造函数初始化,可以看到通过默认的构造函数构造的ReentrantLock是非公平锁。
private final Sync sync; public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }公平锁的获取
ReentrantLock获取锁方法如下:
public void lock() { sync.lock(); }公平锁调用的是FairSync的lock方法:
final void lock() { acquire(1); }acquire方法是AQS实现的方法,介绍一下参数的1的意思:AQS规定同步状态state,想要获得锁就去改变同步状态,就是把同步状态加1。acquire方法:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }获取锁的过程:
- 尝试获取锁。
- 尝试获取失败,将当前线程构成Node加入Sync队列。
- 再次尝试获取,若获取失败线程进入等待态,等待唤醒。
tryAcquire(arg)
公平锁尝试获取,在FairSync里实现,获取同步状态成功返回true,否则返回false
