ReentrantLock的实现分为公平锁和非公平锁。下面来介绍一下公平锁和非公平锁,两部分。
公平锁:有线程需要加锁,有在加锁执行的线程,直接入队。
非公平锁:有线程需要加锁,直接尝试获取锁,如果获取失败入队。
一、下面来介绍一下公平锁的实现
//默认构造器就是公平锁
ReentrantLock r = new ReentrantLock();
r.lock();
1、这是公平锁尝试加锁入口代码
//内部类:static final class FairSync extends Sync
final void lock() {
acquire(1);
}
2、执行acquire逻辑。尝试加锁 入队 逻辑
public final void acquire(int arg) {
//方法详解会在后面给出
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
3、尝试加锁tryAcquire
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前state
int c = getState();
//如果当前state是初始状态
if (c == 0) {
//先尝试获取锁
//hasQueuedPredecessors后面解释
//compareAndSetState 尝试进行CAS加锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//设置当前执行线程
setExclusiveOwnerThread(current);
return true;
}
}
//如果当前线程和正在执行线程是同一个线程,则state+1.这也是可重入锁的实现
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
4、入队操作addWaiter
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;
//如果pred==null,说明队列还未初始化,则执行下面enq逻辑
if (pred != null) {
//将node节点的前指针,指向tail
node.prev = pred;
//CAS抢锁
if (compareAndSetTail(pred, node)) {
//将原tail节点的next指针,指向当前node
pred.next = node;
return node;
}
}
//如果队列是空的,执行初始化操作。
enq(node);
return node;
}
5、初始化队列操作enq(node);
//队列的初始化
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//第一次循环,t==null
if (t == null) { // Must initialize
//CAS创建节点。
if (compareAndSetHead(new Node()))
tail = head;
} else {
//当前node的前驱指针指向队列的尾节点
node.prev = t;
//CAS加锁
if (compareAndSetTail(t, node)) {
//原来的tail节点的next指向当前节点。
t.next = node;
return t;
}
}
}
}
6、acquireQueued方法浅析
//这个方法是在入队之前再次做一次加锁操作,如果加锁成功就执行,加锁失败就设置节点状态为SIGNAL,返回中断标记。进行线程等待
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前节点的前驱节点。
final Node p = node.predecessor();
//如果前驱节点是head,说明是第一个节点,可以尝试加锁。
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);
}
}
7、shouldParkAfterFailedAcquire方法浅析
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//如果前驱节点的状态是-1,标识前驱节点在等待被唤醒,返回中断标记
if (ws == Node.SIGNAL)
return true;
//说明前驱节点是超时或者取消的。可以做出队操作
if (ws > 0) {
//去除中断或者超时的节点。
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//标记前驱节点的状态是Node.SIGNAL(-1),下次循环在第一个if判断就会返回
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
以上是加锁入队流程
二、下面来介绍一下解锁流程
1、解锁的入口实现
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//执行tryRelease,下方有代码注释
if (tryRelease(arg)) {
Node h = head;
//如果第一个节点不为空,并且状态不是0。(个人感觉这里应该是-1)
if (h != null && h.waitStatus != 0)
//尝试唤醒线程,下方有这个方法的代码注释
unparkSuccessor(h);
return true;
}
return false;
}
2、解锁操作:tryRelease
protected final boolean tryRelease(int releases) {
//加锁是进行+1 ,加多少次锁,就要进行多少次解锁,所以每次解锁都是-1
int c = getState() - releases;
//校验当前线程是不是正在执行的线程,不是的会抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//最后一次调用解锁操作
if (c == 0) {
free = true;
//清空正在执行的线程。
setExclusiveOwnerThread(null);
}
//设置state为初始化状态
setState(c);
return free;
}
3、线程唤醒:unparkSuccessor
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//如果head节点的状态是小于0的,直接加锁。
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//获取next节点,如果next==null,或者是取消状态,则循环更新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;
}
// 如果next节点不是空的,则唤醒,准备在当前节点执行完成后进行加锁。
if (s != null)
LockSupport.unpark(s.thread);
}
三、非公平锁
1、非公平锁的创建于入口代码
//ReentrantLock构造器默认值是true,传入false代表实现非公平锁
ReentrantLock r = new ReentrantLock(false);
r.lock();
//非公平锁就是当有线程尝试加锁,不管队列中有无节点,先尝试获取锁。
//获取到就将可以执行线程设置为自己,获取不到在进行入队操作,入队操作和公平锁相同。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
四、下面说一下node节点的状态waitStatus
// CANCELLED:由于超时或中断,此节点被取消。节点一旦被取消了就不会再改变状态。特别是,取消节点的线程不会再阻塞。
static final int CANCELLED = 1;
// SIGNAL:此节点后面的节点已(或即将)被阻止(通过park),因此当前节点在释放或取消时必须断开后面的节点
// 为了避免竞争,acquire方法时前面的节点必须是SIGNAL状态,然后重试原子acquire,然后在失败时阻塞。
static final int SIGNAL = -1;
// 此节点当前在条件队列中。标记为CONDITION的节点会被移动到一个特殊的条件等待队列(此时状态将设置为0),直到条件时才会被重新移动到同步等待队列 。(此处使用此值与字段的其他用途无关,但简化了机制。)
static final int CONDITION = -2;
//传播:应将releaseShared传播到其他节点。这是在doReleaseShared中设置的(仅适用于头部节点),以确保传播继续,即使此后有其他操作介入。
static final int PROPAGATE = -3;
//0:以上数值均未按数字排列以简化使用。非负值表示节点不需要发出信号。所以,大多数代码不需要检查特定的值,只需要检查符号。
//对于正常同步节点,该字段初始化为0;对于条件节点,该字段初始化为条件。它是使用CAS修改的(或者在可能的情况下,使用无条件的volatile写入)。
ps:学习是一个循序渐进的过程。不记录永远记不住。此篇文章是自己学习记录的过程,如果对你有帮助,请帮忙点赞。如果有哪里讲的不对的地方,欢迎一起讨论。