ReentrantLock 源码分析以及 AQS (一)

JDK1.5 之后发布了JUC(java.util.concurrent),用于解决多线程并发问题。AQS 是一个特别重要的同步框架,很多同步类都借助于 AQS 实现了对线程同步状态的管理。

AQS 中最主要的就是独占锁和共享锁的获取和释放,以及提供了一些可中断的获取锁,超时等待锁等方法。

ReentranLock 是基于 AQS 独占锁的一个实现。ReentrantReadWriteLock 是基于 AQS 共享锁的一个读写锁实现。本来打算一篇文章里面写完独占锁和共享锁,但是发现篇幅太长了,也不易于消化。

因此,本篇就先结合 ReentrantLock 源码分析 AQS 的独占锁获取和释放。以及 ReentrantLock 的公平锁和非公平锁实现。

下一篇再写 ReentrantReadWriteLock 读写锁源码,以及 AQS 共享锁的获取和释放。

在正式讲解源码之前,墙裂建议读者做一些准备工作,最好对以下知识有一定的了解,这样阅读起来源码会比较轻松(因为,我当初刚开始接触多线程时,直接看 AQS 简直是一脸懵逼,就像读天书一样。。)。

了解双向链表的数据结构,以及队列的入队出队等操作。

LockSupport 的 park,unpark 方法,以及对线程的 interrupt 几个方法了解(可参考:LockSupport的 park 方法是怎么响应中断的?)。

对 CAS 和自旋机制有一定的了解。

AQS 同步队列

AQS 内部维护了一个 FIFO(先进先出)的双向队列。它的内部是用双向链表来实现的,每个数据节点(Node)中都包含了当前节点的线程信息,还有它的前后两个指针,分别指向前驱节点和后继节点。下边看一下 Node 的属性和方法:

static final class Node { //可以认为是一种标记,表明了这个 node 是以共享模式在同步队列中等待 static final Node SHARED = new Node(); //也是一种标记,表明这个 node 是以独占模式在同步队列中等待 static final Node EXCLUSIVE = null; /** waitStatus 常量值 */ //说明当前节点被取消,原因有可能是超时,或者被中断。 //节点被取消的状态是不可逆的,也就是说此节点会一直停留在取消状态,不会转变。 static final int CANCELLED = 1; //说明后继节点的线程被 park 阻塞,因此当前线程需要在释放锁或者被取消时,唤醒后继节点 static final int SIGNAL = -1; //说明线程在 condition 条件队列等待 static final int CONDITION = -2; //在共享模式中用,表明下一个共享线程应该无条件传播 static final int PROPAGATE = -3; //当前线程的等待状态,除了以上四种值,还有一个值 0 为初始化状态(条件队列的节点除外)。 //注意这个值修改时是通过 CAS ,以保证线程安全。 volatile int waitStatus; //前驱节点 volatile Node prev; //后继节点 volatile Node next; //当前节点中的线程,通过构造函数初始化,出队时会置空(这个后续说,重点强调) volatile Thread thread; //有两种情况。1.在 condition 条件队列中的后一个节点 //2. 一个特殊值 SHARED 用于表明当前是共享模式(因为条件队列只存在于独占模式) Node nextWaiter; //是否是共享模式,理由同上 final boolean isShared() { return nextWaiter == SHARED; } //返回前驱节点,如果为空抛出空指针 final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } Node() { // Used to establish initial head or SHARED marker } Node(Thread thread, Node mode) { // Used by addWaiter this.nextWaiter = mode; this.thread = thread; } Node(Thread thread, int waitStatus) { // Used by Condition this.waitStatus = waitStatus; this.thread = thread; } }

另外,在 AQS 类中,还会记录同步队列的头结点和尾结点:

//同步队列的头结点,是懒加载的,即不会立即创建一个同步队列, //只有当某个线程获取不到锁,需要排队的时候,才会初始化头结点 private transient volatile Node head; //同步队列的尾结点,同样是懒加载。 private transient volatile Node tail; 独占锁

这部分就结合 ReentrantLock 源码分析 AQS 的独占锁是怎样获得和释放锁的。

非公平锁

首先,我们从 ReentrantLock 开始分析,它有两个构造方法,一个构造,可以传入一个 boolean 类型的参数,表明是用公平锁还是非公平锁模式。另一个构造方法,不传入任何参数,则默认用非公平锁。

public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }

NonfairSync 和 FairSync 都继承自 Sync ,它们都是 ReentranLock 的内部类。 而Sync 类又继承自 AQS (AbstractQueuedSynchronizer)。

static final class NonfairSync extends Sync { } static final class FairSync extends Sync { } abstract static class Sync extends AbstractQueuedSynchronizer { }

知道了它们之间的继承关系,我们就从非公平锁的加锁方法作为入口,跟踪源码。因为非公平锁的流程讲明白之后,公平锁大致流程都一样,只是多了一个条件判断(这个,一会儿后边细讲,会做对比)。

NonfairSync.lock

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsgyp.html