大白话讲ReentrantLock源码
什么是ReentrantLock
ReentrantLock是一个基于AQS(抽象队列同步器)实现的一个同步锁,有公平和非公平两种状态,本节内容只讲公平锁内容。
实现ReentrantLock的核心有三个:
1.CAS (Compare And Swap):比较并交换,用来保证无论并发有多高,都只有一个线程
能够执行成功;
2.自旋 :其实就是死循环;
3.LockSupport的park()和unpark() :作用是用于停止线程和唤醒
一.ReentrantLock构造器
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public ReentrantLock() {
sync = new NonfairSync();
}
ReentrantLock类有两个构造器,一个有参,一个无参。无参构造默认创建的时非公平锁.而有参构造,参数是一个boolean值,当参数为true时,创建的是公平锁,为false时,创建的是非公平锁;
公平锁:无论哪个线程过来都要排队,讲究先到先得;
非公平锁:线程进来会先尝试抢占锁,抢占失败才会入队;
二.线程执行lock()方法
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
可以看到,当线程执行lock()方法时,其实就是执行 acquire(arg) 这个方法,这个方法里面有四个方法(先说作用,再细讲):
tryAcquire(...)
:尝试获取锁。如果获取锁成功就不会执行下面的方法了;
addWaiter(...)
:入队方法,获取锁失败会调用这个方法,里面有个自旋,能够保证入队一定成功;
acquireQueued(...)
:阻塞方法;调用的是LockSupport.park(…)来进行阻塞线程;
selfInterrupt()
:只有当线程被中断唤醒时,才会调用这个方法,目的是给线程加中断信号;
不过讲方法前还要先把几个重要的东西过一遍:
private volatile int state;
private transient Thread exclusiveOwnerThread;
private transient volatile Node tail; //队列的尾节点
private transient volatile Node head; //队列的头节点
1.状态器state
:用来标注锁是否被持有,被重入了几次. 初始值为0,每重入一次+1
2.exclusiveOwnerThread
: 存放的是持有锁的线程
static final class Node {
static final Node SHARED = new Node(); //独占
static final Node EXCLUSIVE = null; //共享
//下面四个是节点的生命状态(waitStatus),默认是0
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;
还有一个就是CLH队列(同步队列)的节点Node类,一个Node类可以看作由四部分组成,分别是 prev, next, waitStatus, thread (见下图);
那么接下来就是重点讲这些方法了!!!
1. tryAcquire(1)方法
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
tryAcquire(1)
这个方法:先判断状态器是否为0,如果为0,则说明该锁还未被持有,接着判断队列中有没有元素,没有就用CAS把状态器设置为1,如果设置成功,说明该线程获取锁成功了,就把变量 exclusiveOwnerThread设置为当前线程。
如果上面的都没有成功,就进入下面的判断,先判断变量 exclusiveOwnerThread 是否是当前线程,为当前线程则抢锁成功,这里就体现了 ReentrantLock 的可重入性,获得锁成功后只需把状态器在原来的基础上 +1 即可;
如果上面两种情况都不符合,就抢锁失败,返回 false,准备进入入队方法。
2.addWaiter(Node.EXCLUSIVE)方法
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
来到入队方法时,先创建一个当前线程的节点,判断一次尾节点是否为空,不为空,就把当前节点的前驱指针( prev )指向前驱节点,接着使用 CAS 把尾节点指向当前节点,如果 CAS 设置成功,就把前驱节点的后驱指针指向当前节点,入队成功;
如果上面的都没成功,就执行 enq(node)
方法:进入自旋,判断尾节点是否为空,为空则使用 CAS 创建一个空节点,并把头节点 head 和尾节点 tail 都指向这个空节点。
如果尾节点不为空,则重复之前的步骤;
3.acquireQueued(…)方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
acquireQueued(...)
方法的作用是阻塞,当然在阻塞前会再次去抢锁;直接看代码:
进入自旋,将当前节点的前驱节点赋给常量 p ,判断前驱节点是否为头节点并且尝试抢锁,如果都为 true ,说明抢锁成功,需要将当前节点设为头节点,并把当前的thread 和前驱指针置空,以及前驱节点的后驱指针置空。
如果前面没满足,则进入下面的 if 条件,先判断前驱节点的生命状态为多少,第一次循环,waitStatus 肯定是默认值0,也就进入最后一个else ,这里是把前驱节点的生命状态(waitStatus)设为 SIGNAL(-1) ;设置成功后进入第二次循环,这时候shouldParkAfterFailedAcquire(...)
这个方法就返回true了。
随后进入 parkAndCheckInterrupt()
方法,里面的LockSupport.park(this)
代码就是用于阻塞线程的,这个方法的唤醒方式可以是 LockSupport.unpark()
,也可以是线程中断thread.interrupt()
,当使用线程中断唤醒时,这里就会返回true ,就会进入最后一个方法selfInterrupt()
4.selfInterrupt()方法
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
这个方法只有被中断唤醒的线程才能进入,它的目的是为了重新标记线程的中断信号,让我们的代码逻辑可以进行判断;也就是Thread.interrupted()
;