从ReentrantLock的lock和unlock方法理解AQS运行过程
程序员文章站
2022-05-05 23:12:14
...
目录
0.来源说明
本文是对尚硅谷2020最新版Java面试题第三季(java大厂面试题,周阳主讲)中的AQS 视频资料的学习整理
1.ReentrantLock的原理
2.公平锁与非公平锁
3. 从非公平锁的lock和unlock方法理解AQS
public class AQSDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
//通过一个银行办理业务的案例来模拟AQS是如何进行线程管理和通知唤醒机制
//3个线程模拟三个来银行办理业务的顾客
//A顾客,第一个顾客,此时银行窗口没有任何人,A可以直接办理业务
//办理业务时间20分钟
new Thread(()->{
lock.lock();
try {
System.out.println("A thread come in.......");
TimeUnit.MINUTES.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "A").start();
//B顾客,第二个顾客,由于银行窗口只有一个, 此时B只能等待
//进入到候客区
new Thread(()->{
lock.lock();
try {
System.out.println("B thread come in.......");
} finally {
lock.unlock();
}
}, "B").start();
//C顾客,第三个顾客,由于银行窗口只有一个,此时C只能等待
//进入候客区
new Thread(()->{
lock.lock();
try {
System.out.println("C thread come in.......");
} finally {
lock.unlock();
}
}, "C").start();
}
}
AQS源码深度分析走起
- lock()---->acquire()--->tryAcquire(arg)--->addWaiter(Node.EXCLUSIVE)--->acquireQueued(addWaiter(Node.EXCLUSIVE))
- unlock()
A线程中lock.lock()方法
A顾客想要办理业务(对应调用A线程中lock.lock()),由于此时受理窗口没人,可直接办理业务。
运行完红框代码后,状态如下:
A顾客办理业务需要20分钟,将一直占着业务办理窗口。
B线程中的lock.lock()方法
B顾客想要办理业务(对应B线程中的lock.lock())
由于此时A顾客在占着窗口,B将进入acquire(1)分支。
tryAcquire(arg)方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//current线程为B线程
final Thread current = Thread.currentThread();
//由于A线程占用着,因此state值为1
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//current线程为A线程,ownerThread为A线程,所以不相等
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//因此,最终返回false
return false;
}
由于返回false,!tryAcquire(1)为true,继续进行if判断。
addWaiter(Node.EXCLUSIVE)方法。
private Node addWaiter(Node mode) {
//当前线程为B线程
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;
}
}
//由于初始时tail为null,因此将进入enq方法
enq(node);
return node;
}
//完成的工作就是把B线程的节点加入到队列中
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//如果tail为null,说明队列中没有Node,需要初始化,生成哨兵节点
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
//第二次for循环将走到这里
//将B线程的Node加入到队列中
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
此时的图示如下:
acquireQueued()方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* 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.
*/
//这里会将哨兵节点的waitStatus设置为signal
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
C线程中lock.lock()方法
C线程的运行过程和B线程一样,最终B、C线程都将挂起阻塞在LockSupport.part(this)那个地方,不会再继续向下执行
A线程中lock.unlock()
public void unlock() {
sync.release(1);
}
B线程(C线程同理)
程序继续向下执行,将进行下一次for循环,运行到如下红框代码,将B线程的Node从队列中移除。
上一篇: JAVA面试-基础1