Lock
上文已经通过文字和画图大致描述了Lock的整个流程,本文就来看源码
相关UML图
解析,上图自然懂
组合了一个Sync类的对象,在构造方法里面new的
ReentrantLock默认是非公平锁
如果要new公平锁,可以通过传入参数true实现
Sync继承AQS,Sync其实就是干实事的,ReentrantLock的方法都是通过调用它实现锁的机制
具体的非公平锁和公平锁继承抽象类Sync
公平锁和非公平锁
公平锁和非公平锁体现是在一个锁释放后,新来的线程在临界点能否抢占到锁而不是一定让AQS队列里面等待的线程抢到锁,如果能就是非公平,如果不能,就是公平的,
注意:公平与不公平不体现在AQS队列等待的线程,因为AQS队列等待的线程始终是头结点被唤醒
根据上面的UML图,可以知道ReentrantLock具体是靠sync对象工作的,而sync(父类的对象)的具体实现又分为非公平锁(NonfairSync)和公平锁(FairSync),这就会导致lock方法调用的一系列的不同逻辑
- 公平锁的逻辑
final void lock() {
acquire(1); //传参数1抢占1把锁.
}
public final void acquire(int arg) { // AQS里面的方法
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { //表示无锁状态
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires))
{ //CAS(底层#Lock指令来加锁) -> 原子操作| 实现互斥 的判断
//hasQueuedPredecessors()判断是否有人在阻塞队列前面排队,
//如果没有就返回false,就代表可以去抢占!
//因为要保证公平嘛,如果存在排队的队列,又因为要公平,所以就不能和那些排队的队列中的线程去竞争
setExclusiveOwnerThread(current); //把获得锁的线程保存到 exclusiveOwnerThread中
return true;
}
}
//有锁状态,且当前获得锁的线程和当前抢占锁的线程是同一个,表示重入!
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; //增加重入次数.
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc); //保存state
return true; }
return false;
}
- 非公平锁的逻辑
final void lock() {
//不管当前AQS队列中是否有排队的情况,先去插队
if (compareAndSetState(0, 1)) //返回false表示抢占锁失败
//成功,直接设置当前线程占有锁
setExclusiveOwnerThread(Thread.currentThread());
else acquire(1);
}
public final void acquire(int arg) { //AQS里面的方法 跟公平锁一个逻辑这里
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//非公平锁这是第二次抢占
//C==0说明是无锁状态
if (c == 0) {
//注意对比,没有hasQueuedPredecessors
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;
}
抢占失败,加入队列并进行自旋等待
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
源码可以看到tryAcquire返回false就代表抢占锁失败,那么就会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
- addWaiter(Node.EXCLUSIVE) -> 添加一个互斥锁的节点
- acquireQueued() -> 自旋锁和阻塞的操作
先来看添加
如果CAS失败或者阻塞队列未初始化就进入enq()方法中。
enq()方法中,我们在第一次进入这个方法的时候,下面图一所示,tail和head都指向null;
第一次循环,到首先会到图二,然后判断t所指向的节点是不是null,如果是的话,就用CAS更新节点,这个CAS我们可以看作:头节点head为null,我们把head节点更新为一个哨兵节点(哨兵节点就是new Node()),再将tail也指向head,就是图三了
第二次for循环:走到上面的else语句,将新节点的前一个节点设置为哨兵节点;
然后就是CAS更新节点,这里CAS的意思:如果最后的节点tail指向的和t是一样的,那么就将tail指向node节点
最后再将t的下一个节点设置为node,下图所示,就ok了
再来看自旋和阻塞
//node表示当前来抢占锁的线程,有可能是ThreadB、 ThreadC。。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { //自旋
//begin ->尝试去获得锁(如果是非公平锁的话)
final Node p = node.predecessor(); //获得当前结点的前置节点
if (p == head && tryAcquire(arg)) { //如果前置节点是头结点,则自己成为了可以唤醒的结点了
//执行tryAcquire(arg)
//即尝试抢占锁(上面已经看过了,非公平和公平有两种实现)
//返回true,则抢占成功,不需要等待,直接返回。
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//end
//否则,判断是否应该让线程去阻塞(park)
//shouldParkAfterFailedAcquire(p, node)返回true
//则执行后面的parkAndCheckInterrupt(通过LockSupport.park)
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)//如果是signal状态,就让它阻塞
return true;
if (ws > 0) {//是Cancle状态,则直接丢弃这个结点
do {
node.prev = pred = pred.prev;//从后往前去操作,防止线程安全问题
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//把前置结点设为SIGNAL
}
return false;
}
注意这个waitStatus(默认为0),它决定是否要被阻塞
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
//ThreadB、 ThreadC、ThreadD、ThreadE -> 都会阻塞在下面这个代码的位置.
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
unlock
public final boolean release(int arg) {
if (tryRelease(arg)) {
//进入if里面说明此时已经真的释放锁了(逻辑要看下面)
Node h = head;//得到AQS队列当前的head结点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//调用unpark进行唤醒,见下面
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
//如果不是当前持有锁的线程来操作的话就直接报监视异常
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//等于0就代表真的释放了,否则的话只是减小1,即减少1次重入数
free = true;
setExclusiveOwnerThread(null);
}
setState(c);//这里都不用CAS了,因为调用释放,说明肯定是互斥的了
//只有这个线程在占用锁(前面已经检查过了)
return free;//这里如果返回true则说明上面应该进入if里面
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)//小于0,-1表示可以唤醒状态
compareAndSetWaitStatus(node, ws, 0);//恢复成0
Node s = node.next;
if (s == null || s.waitStatus > 0) {//说明ThreadB这个线程可能已经被销毁了,或者出现异常了
//则移除这个结点
//从尾往前遍历
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)//查找到小于等于0的结点
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}