ReentantLock源码学习
1.ReentantLock类结构解析
public class ReentrantLock implements Lock,
ReentrantLock实现了锁接口,内部有一个抽象类Sync继承了AbstractQueuedSynchronizer(AQS)。
abstract static class Sync extends AbstractQueuedSynchronizer
ReentrantLock实现了两种锁机制,公平锁与非公平锁。
static final class NonfairSync extends Sync
static final class FairSync extends Sync
使用公平锁或非公平锁是由创建ReentranLock实例时传参指定。
//默认为非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//传true代表使用公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.分析Lock()与unlock()方法
lock方法是定义在Sync抽象类中的一个抽象方法 ,abstract void lock();,lock方法是请求锁Acquires the lock.我们一般使用如下:
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
reentrantLock.unlock();
}
//跟入lock()方法-->
public void lock() {//这个就是ReentrantLock中的lock,可以看到是调用内部抽象类Sync的lock
sync.lock();
}
//根据ReentrantLock的构造方法可知,这里实际使用的是非公平锁,所以应该去看NonfairSync类中的lock方法(多态的体现)
final void lock() {
//cas 设置state为1(state是调用AbstractQueuedSynchronizer的表示锁状态)
if (compareAndSetState(0, 1))
//改状态成功,表示获取到锁,设置持有锁的线程为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//获取锁失败,调用AbstractQueuedSynchronizer(Sync继承的)中的acquire方法。后面分析。
}
这是非公平锁的,下面我们看看公平锁的lock方法
final void lock() {
acquire(1);//直接调用调用AbstractQueuedSynchronizer的中的acquire方法
}
unlock方法:非公平锁和公平锁的unlock方法一样
public void unlock() {
sync.release(1);//调用AbstractQueuedSynchronizer中的release方法 后面分析
}
我们可以看到,加锁解锁最终实现的关键代码基本都在AbstractQueuedSynchronizer类中,所以说AQS是并发编程的核心。
3.AbstractQueuedSynchronizer(AQS)解析
这个类是并发编程的核心,很多并发的实现都是由他来完成的。这里我们只介绍与ReentrantLock相关部分。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
3.1AbstractQueuedSynchronizer类结构
内部类Node:
static final class Node {
//标记表示节点处于共享模式
static final Node SHARED = new Node();
//标记表示节点处于独占(排他)模式
static final Node EXCLUSIVE = null;
//这两个是我们今天ReentrantLock加解锁可以看到的
//waitStatus值,表示线程已取消
static final int CANCELLED = 1;
//waitStatus值,表示后续线程需要唤醒
static final int SIGNAL = -1;
//waitStatus值,表示线程正在等待条件
static final int CONDITION = -2;
//waitStatus值,表示下一个acquireShared应无条件传播
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
//获取前驱节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { //无参构造
}
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;
}
}
一些成员变量说明:
//等待队列的头,延迟初始化。除初始化外,只能通过方法setHead修改。注意:如果head存在,保证其waitStatus不被取消
private transient volatile Node head;
//等待队列的尾部,延迟初始化。仅通过方法enq修改以添加新的等待节点。
private transient volatile Node tail;
//同步状态 可以理解为锁,0代表无锁
private volatile int state;
下面我们来看看一些关键方法的代码:
3.2 关键方法
3.2.1 加锁过程
首先我们看看加锁时调用的acquire方法
public final void acquire(int arg) {//arg == 1 ,公平锁非公平锁都是传1可以看前面的调用。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
第一步调用tryAcquire(arg)方法
//tryAcquire这个方法被重写了,我们需要去看重写的方法,首先是公平锁的方法(在FairSyn类中)
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获得同步状态(锁状态)
int c = getState();
if (c == 0) {//没有线程占用锁,state == 0
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//获取到锁时,设置占有锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//当前线程就是持有锁的线程
int nextc = c + acquires;// state+1 这里就是可重入锁的概念
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);//设置锁状态
return true;
}
return false;//没有获取到锁
}
//head==tail表示未初始化,此时没有等待(之前注释的延迟初始化)没有线程等待返回false
//head!=tail表示已经初始话执行&&后表达式
//(s = h.next) == null 判断head的下一个节点是否存在(真正开始排队的节点),条件成立时表示没有等待线程。
//条件不成立执行 s.thread != Thread.currentThread(),判断等待线程是不是当前线程,不是返回ture,是返回false
// 我们可以看出返回false的均表示不需要等待,因为外层调用时!hasQueuedPredecessors()
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
//CAS改变锁状态
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
//AbstractOwnableSynchronizer(AQS继承的抽象类)中方法
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
//非公平锁实现
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//基本与公平锁实现一致,只是当没有线程持有锁(state==0)时,不需要查看队列,直接CAS加锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//获取锁失败进入第二步进行队列相关操作
//维护队列
private Node addWaiter(Node mode) {//mode Node.EXCLUSIVE==null
Node node = new Node(Thread.currentThread(), mode);//构造新节点,保存当前线程
// 记录尾节点
Node pred = tail;
if (pred != null) {//有尾节点,表示有线程在等待队列
node.prev = pred;//入队将当前节点的前驱节点指向尾节点
if (compareAndSetTail(pred, node)) {//CAS设置当前线程节点为尾节点
pred.next = node;//前驱节点next指向当前线程节点
return node;//返回当前节点
}
}
enq(node);//队列未初始化进入(无线程等待)
return node;
}
private Node enq(final Node node) {
for (;;) {//死循环是为了保证初始化后将node加入队列,和保证CAS成功。
Node t = tail;//记录尾节点
if (t == null) { // 未初始化
if (compareAndSetHead(new Node()))//CAS设置头结点(初始化队列)
tail = head;
} else {//维护队列 与addWaiter相似
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
//以独占不中断模式获取已在中的线程队列,用于条件等待方法以及获取。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {//这里死循环是unpark唤醒后,再次去执行获取锁。。。
final Node p = node.predecessor();//获取当前线程的前驱节点
//如果p是head节点表示node是第一个排队节点,则再次尝试获取锁
if (p == head && tryAcquire(arg)) {//p是头结点,并且获取到锁了(此时有线程释放了锁)
setHead(node);//移动指向头结点的引用,看下方方法解释。
p.next = null; // help GC (断掉另一条引用,此时老头结点就没有引用了,可以被回收)
failed = false;//成功了,所以不失败(废话)
return interrupted;
}
//注意这里只有P不是头结点情况执行(前面已经有线程排队了)
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//获取锁失败可以park,去park当前线程
interrupted = true;
}
} finally {//代码抛异常执行
if (failed)//正常情况不会,不做分析,属于异常处理机制。
cancelAcquire(node);
}
}
private void setHead(Node node) {
head = node;//将head节点指向当前node
node.thread = null;//因为线程已经获取了锁,持有锁的线程不参与排队,(头结点线程永远为null)
node.prev = null;//断开新头结点指向老头结点的引用(双向链表,这里只断了一条)
}
//检查并更新未能获取锁的节点的状态。如果线程阻塞,则返回true。这里是观察前一个节点的状态来判断自己是否需要排队
//这是AQS队列的设计理念,有点像银行取钱,并且你只能看到你前面一个人。这个ws就是标志他的状态,通过观察前一个的状态,判断自己应该怎么做。ws==-1说明前面的人也在排队,所以你也得排队。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; //ws第一次进入是0,int型初始化值(外部调用函数在循环)
if (ws == Node.SIGNAL)//Node.SIGNAL==-1
//表示(当前线程节点前一个节点)已设置状态,前面的在等待,因此它可以安全地park
return true;
if (ws > 0) {//条件队列的,不做具体分析
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//CAS设置pred节点的值为-1,
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//调用后当前线程就停在这里(想象debug停在这里)
return Thread.interrupted();//检查是否中断
}
到这里lock加锁过程已经分析完了,这里有总结的一张流程图,帮助理解和分析。
3.2.1 解锁过程
相对于加锁方法解锁就比较简单了
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {//这个方法多个类重写了,找ReentrantLock里的方法,如下
Node h = head;
if (h != null && h.waitStatus != 0)//头结点不为空且它的ws不等于0(队列里还有等待的)
unparkSuccessor(h);//唤醒线程
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//拿到当前的锁状态(同步状态) - 1(锁重入,逐级解锁)
//判断当前线程是不是拥有锁的线程,不是的话就抛异常。
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//最后一层锁,解锁完成,设置持有锁的线程为空
free = true;
setExclusiveOwnerThread(null);
}
setState(c);//设置锁状态
return free;
}
private void unparkSuccessor(Node node) {
//注意这里是当前头节点的ws
int ws = node.waitStatus;
if (ws < 0)//如果状态为负,清除锁状态
compareAndSetWaitStatus(node, ws, 0);
//unpark的线程保存在后续节点中,通常只是下一个节点。但如果取消或明显为空,
//则从尾部向前遍历以找到实际的未取消的后续节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//唤醒线程
}
下面是解锁过程的流程图:
3.3 AQS队列加锁过程详解
由于多线程过程中场景比较复杂,不可能每一种情况都分析,这里只分析两种最基础的,基本可以覆盖源码,这里建议打开源码,对照源码,或者上面加锁过程流程图,进入队列部分进行阅读,充分理解AQS队列。
3.3.1 场景一
场景1:持有锁的线程为t1,当前线程为t2(获得cpu),t2未获取到锁,调用addWaiter,
(step1):创建node2。此时队列未初始化,调用enq(node2).队列未初始化,tail==null,
(step2):调用compareAndSetHead(new Node())(创建node1并设置为head)。
(step3):tail = head;此时队列初始化完成,队列中有一个节点。由于在循环中,进行下一次循环。
(step4):此时tail != null,node.prev = t;
(step5):CAS设置node2为新的tail,compareAndSetTail(t, node),
(step6):将node1.next指向node2,t.next = node;(t是一个临时变量。每一次循环开始赋值,现在t是node1),返回node2
(step7): acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),node2.prev是head结点,但此时线程t1持有锁未释放,所以进入判断shouldParkAfterFailedAcquire(p, node),node2.prev.ws==0,CAS设置其为-1.进入下一次循环,再次进入本方法。
(step8):node2.prev.ws==-1,返回true进入parkAndCheckInterrupt(),调用LockSupport.park(this);将当前线程t2停住。
3.3.2 场景二
场景二:持有锁的线程为t1,当前线程为t3,aqs队列里t2在排队(注意这里面有两个节点,头结点是没有线程的,持有锁的线程不参与排队)。
(step1):获取锁失败,调用addWaiter(Node.EXCLUSIVE), arg),创建新节点node3.
(step2):tail != null,将node3.prev指向当前尾节点node2.
(step3):CAS设置当前节点node3为尾节点。
(step4):pred.next = node;(node2.next =node3),返回node3.
(step5):acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),此时node3.prev != head。调用shouldParkAfterFailedAcquire(p, node),两次循环将node2.ws设置为-1并返回true。
(step6):进入parkAndCheckInterrupt(),调用LockSupport.park(this);,将当前线程t3停住。
(注:绿色为新增操作,虚线部分是原本有的引用改动后断开了)
未完待续…
(如有错误欢迎各位同学评论留言指正)