我们谈到并发,就不得不谈ReentrantLock
锁;而谈到ReentrantLock
锁,不得不谈一下AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch…。 我们以ReentrantLock
作为讲解切入点。
ReentrantLock
源码分析static abstract class Sync extends AbstractQueuedSynchronizer
final static class NonfairSync extends Sync
final static class FairSync extends Sync
显然是为了支持公平锁和非公平锁而定义,默认情况下为非公平锁,设置为公平锁可直接在构造函数中传入true即可。
先理一下Reentrant.lock()方法的调用过程(默认非公平锁):
ReentrantLock
类中的lock()
方法和uplock()
方法都做了些什么事情。// 首先会进入ReentrantLock中调用lock()方法,接着调用AQS中lock()方法
(1)、 public void lock() {
// 非公平锁 调用无参构造函数
sync.lock();
}
// 对所持有资源线程加锁
(2)、 final void lock() {
// 这里有两个值 期望值和设置值
if (compareAndSetState(0, 1)) // 这一步CAS会判断当前锁是否被占用
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 若是锁已持有,则会走这一步
}
// 获取锁资源
(3)、public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取锁状态失败
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 从上一步可以看出,会将当前线程进入等待状态,后续将子线程执行完才会去释放锁资源
(4)、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; // 若是尾节点不为空,则通过CAS设置当前节点为尾节点并返回
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node); // 否则进入enq()方法
return node;
}
// 这个方法会解决两个问题 1、尾节点为空 2、CAS尝试设置锁失败
(5)、private Node enq(final Node node) {
for (;;) {
// 自旋直到获取到尾节点
Node t = tail;
if (t == null) {
// Must initialize
if (compareAndSetHead(new Node())) // 若是尾节点为空,则创建一个新节点
tail = head; // 将父节点设置为尾节点
} else {
// 解决上面CAS设置失败,重新一直尝试去获取
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
// 从等待队列中去获取锁资源
(6)、 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;
}
// 尝试获取锁失败,会将当前线程挂起,设置状态为SIGNAL,等待其他线程执行完,才会被唤醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
// 将尝试获取锁失败的线程挂起
(7)、 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 初次进入waitStatus为0,会直接进入else将状态设置为SIGNAL
if (ws == Node.SIGNAL) // 若状态为SIGNAL则直接返回
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
// 状态为CANCELLED,通过自旋,将是该状态的都会移除
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 设置状态SIGNAL
}
return false;
}
// 将当前线程阻塞并挂起
(8)、 private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 等待线程被唤醒
return Thread.interrupted();
}
// 尝试获取锁状态
(9)、protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires); // 使用非公平锁方式获取锁状态
}
// 非公平锁获取锁状态
(10)、final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); // 获取state
if (c == 0) {
// 当前state为0,则锁没有被占用,尝试CAS设置锁状态为1
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); // 设置线程归当前线程持有
return true;
}
}
// 当出现同一个持有锁的线程,再次获取锁,就会进行锁重入过程
else if (current == getExclusiveOwnerThread()) {
// 这里会进行锁重入
int nextc = c + acquires; // state + 1
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc); // 设置锁状态
return true;
}
return false; // 否则获取锁状态失败,符合FIFO规则
}
lock()
方法以后,我们来看一下uplock()
方法中源码解析// 1、首先uplock会去调用AQS里面的release()方法
public void unlock() {
sync.release(1);
}
// 2、AQS中release()方法
public final boolean release(int arg) {
// arg = 1
if (tryRelease(arg)) {
// 若state状态为0则返回true,否则返回false
Node h = head;
if (h != null && h.waitStatus != 0) // 若头节点不为空且等待状态不为cacelled
unparkSuccessor(h); // 将头节点的后续节点唤醒
return true;
}
return false;
}
// 3、tryRelease()方法在ReentrantLock类中调用
protected final boolean tryRelease(int releases) {
// releases = 1
int c = getState() - releases; // 持有锁state = 1,否则为0,所以这里释放之后为0
if (Thread.currentThread() != getExclusiveOwnerThread()) // 判断当前线程是否是持有锁资源的线程,若不是直接抛异常
throw new IllegalMonitorStateException();
boolean free = false;
// 如果前面state状态相减之后不为0,则当前线程发生了锁重入
if (c == 0) {
free = true;
setExclusiveOwnerThread(null); // 将线程锁持有者释放并设置为null
}
setState(c); // 重新设置state状态,在这里也有可能发生锁重入
return free;
}
// 4、调用AQS中的unparkSuccessor()方法
private void unparkSuccessor(Node node) {
int ws = node.waitStatus; // 获取当前节点的等待状态
if (ws < 0) // 判断等待状态是否小于0
compareAndSetWaitStatus(node, ws, 0); // 若小于0则将waitStatus设置为0
Node s = node.next; // 获取当前节点的下一个节点
// 下一个节点为空或waitStatus状态为cancelled
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾节点开始寻找;尾节点不为空且尾节点不是当前节点;一直自旋至头节点的下一个节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0) // 并且该节点的状态为SIGNAL
s = t;
}
if (s != null) // 判断当前节点的下一个节点是否为空
LockSupport.unpark(s.thread); // 若不为空,则唤醒该线程
}
在这里通过对uplock()
源码分析可知:首先会判断当前线程锁持有者是否被释放,若是state = 0
,表明锁被释放且获取头节点,判断头节点是否为空且等待状态不为cacelled
,则会将头节点中的后续节点唤醒,否则继续会让后续节点挂起状态,等待被唤醒。
1、ReentrantLock
锁中最主要的就是lock()
方法和uplock()
方法,通过对这两个方法源码分析,你可以发现,在并发过程中它是怎么去加锁和释放锁的过程,最主要的就是为什么加锁过程还需要用到CAS
方式去获取锁,若是不使用CAS
获取锁,有会引发什么问题,有没有更好的解决方案?这个你们可以深入考虑一下。
2、通过源码分析中,我们接触到了公平锁与非公平锁之间获取锁过程中有什么不同?各自都有什么优势,哪这里又引入了一个问题,在你们的理解中什么是公平锁和非公平锁?
3、其实,从ReentrantLock
切入点分析过程中,并不是特别难,包括CAS
获取锁和加锁的过程以及引入公平锁与非公平锁之间的实现方式,各自的优势体现在哪方面;主要我们还是要去理解去掌握它的有关实现方式,我们可以想一下有没比这种方式更好的实现方案。
结语:后续会继续讲
AQS
中的CountDownLatch
源码解析~