深圳网站建设学习网站建设购销合同
news/
2025/9/22 15:28:49/
文章来源:
深圳网站建设学习,网站建设购销合同,设计企业品牌logo,建设网站免费支持php1 释放锁流程概述
ReentrantLock的unlock()方法不区分公平锁还是非公平锁。 首先调用unlock()方法。 unlock()底层使用的是Sync.release(1)方法 public void unlock() {!-- -- sync.release(1); } release(1)方法会调用tryRelease(1)去尝试解锁。
public fin…1 释放锁流程概述
ReentrantLock的unlock()方法不区分公平锁还是非公平锁。 首先调用unlock()方法。 unlock()底层使用的是Sync.release(1)方法 public void unlock() {!-- -- sync.release(1); } release(1)方法会调用tryRelease(1)去尝试解锁。
public final boolean release(int arg) {!-- --//尝试释放锁if (tryRelease(arg)) {!-- --Node h head;if (h ! null h.waitStatus ! 0)//如果释放锁成功而且等待队列不为空且有一个以上的等待线程//因为只有下一个线程才能将前一个线程的waitStatus的状态改为-1head表示当前执行的线程//当head不为空且waitStatus 0说明有等待线程初始化了等待队列且将持有锁线程的//等待状态改为了-1必然存在等待线程将队头的第一个唤醒unparkSuccessor(h);return true;}return false;} tryRelease(arg)尝试释放锁
ReservedStackAccessprotected final boolean tryRelease(int releases) {!-- --//释放一次锁就将重入的次数减掉1int c getState() - releases;if (Thread.currentThread() ! getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free false;//如果锁得状态为1则表示锁真正被释放了将持有锁的线程置为nullif (c 0) {!-- --free true;setExclusiveOwnerThread(null);}//否则锁依然被持有因为该锁被持锁线程重入了多次setState(c);return free;} 如果tryRelease(释放锁成功且判断等待队列确实有阻塞线程则尝试唤醒
private void unparkSuccessor(Node node) {!-- --//如果等待的线程状态0SIGNAL将其设为0int ws node.waitStatus;if (ws 0)node.compareAndSetWaitStatus(ws, 0);Node s node.next;//找一个符合条件即真正在阻塞睡眠的线程if (s null || s.waitStatus 0) {!-- --s null;for (Node p tail; p ! node p ! null; p p.prev)if (p.waitStatus 0)s p;}//找到后将其唤醒。有个疑问头节点不变化嘛if (s ! null)LockSupport.unpark(s.thread);} 回答自己的疑问为啥没有操作头节点呢这是因为唤醒阻塞的第一个线程后它会重新去获取锁而不是直接将锁分配给它。 final boolean acquireQueued(final Node node, int arg) {!-- --boolean interrupted false;try {!-- --for (;;) {!-- --final Node p node.predecessor();if (p head tryAcquire(arg)) {!-- --setHead(node);p.next null; // help GCreturn interrupted;}if (shouldParkAfterFailedAcquire(p, node))//从此处被唤醒后重新进行循环尝试去争抢锁如果没抢到则继续阻塞(非公平的时候)//当刚被唤醒循环一次此时phead同时如果tryAcquire(1)去获得锁//如果获得成功将自己设置为head//如果获得锁失败则自己再自旋一次(因为在释放锁的时候head的ws又重置为0了).//如果还是失败则自己再次park()睡眠interrupted | parkAndCheckInterrupt();}} catch (Throwable t) {!-- --cancelAcquire(node);if (interrupted)selfInterrupt();throw t;}} 2 释放锁源码分析
public void unlock() {
// 释放锁资源不分为公平锁和非公平锁都是一个sync对象
sync.release(1);
}
// 释放锁的核心流程
public final boolean release(int arg) {
// 核心释放锁资源的操作之一
if (tryRelease(arg)) {
// 如果锁已经释放掉了走这个逻辑
Node h head;
// h不为null说明有排队的录课时估计脑袋蒙圈圈。
// 如果h的状态不为0为-1说明后面有排队的Node并且线程已经挂起了。
if (h ! null h.waitStatus ! 0)// 唤醒排队的线程
unparkSuccessor(h);
return true;
}
return false;
}
// ReentrantLock释放锁资源操作
protected final boolean tryRelease(int releases) {
// 拿到state - 1并没有赋值给state
int c getState() - releases;
// 判断当前持有锁的线程是否是当前线程如果不是直接抛出异常
if (Thread.currentThread() ! getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// free代表当前锁资源是否释放干净了。
boolean free false;
if (c 0) {
// 如果state - 1后的值为0代表释放干净了。
free true;
// 将持有锁的线程置位null
setExclusiveOwnerThread(null);
}
// 将c设置给state
setState(c);
// 锁资源释放干净返回true否则返回false
return free;
}
// 唤醒后面排队的Node
private void unparkSuccessor(Node node) {
// 拿到头节点状态
int ws node.waitStatus;
if (ws 0)
// 先基于CAS将节点状态从-1改为0
compareAndSetWaitStatus(node, ws, 0);
// 拿到头节点的后续节点。
Node s node.next;
// 如果后续节点为null或者后续节点的状态为1代表节点取消了。
if (s null || s.waitStatus 0) {
s null;
// 如果后续节点为null或者后续节点状态为取消状态从后往前找到一个有效节点环境
for (Node t tail; t ! null t ! node; t t.prev)
// 从后往前找到状态小于等于0的节点
// 找到离head最新的有效节点并赋值给s
if (t.waitStatus 0)
s t;
}
// 只要找到了这个需要被唤醒的节点执行unpark唤醒
if (s ! null)
LockSupport.unpark(s.thread);
}
3 AQS常见的问题
3.1 AQS中为什么要有一个虚拟的head节点 因为AQS提供了ReentrantLock的基本实现而在ReentrantLock释放锁资源时需要去考虑是否需要执行unparkSuccessor方法去唤醒后继节点。 因为Node中存在waitStatus的状态默认情况下状态为0如果当前节点的后继节点线程挂起了那么就将当前节点的状态设置为-1。这个-1状态的出现是为了避免重复唤醒或者释放资源的问题。 因为AQS中排队的Node中的线程如果挂起了是无法自动唤醒的。需要释放锁或者释放资源后再被释放的线程去唤醒挂起的线程。 因为唤醒节点需要从整个AQS双向链表中找到离head最近的有效节点去唤醒。而这个找离head最近的Node可能需要遍历整个双向链表。如果AQS中没有挂起的线程代表不需要去遍历AQS双向链表去找离head最近的有效节点。为了避免出现不必要的循环链表操作提供了一个-1的状态。如果只有一个Node进入到AQS中排队所以发现如果是第一个Node进来他必须先初始化一个虚拟的head节点作为头来监控后继节点中是否有挂起的线程。
3. 2 AQS中为什么选择使用双向链表而不是单向链表 首先AQS中一般是存放没有获取到资源的Node而在竞争锁资源时ReentrantLock提供了一个方法lockInterruptibly方法也就是线程在竞争锁资源的排队途中允许中断。中断后会执行cancelAcquire方法从而将当前节点状态置位1并且从AQS队列中移除掉。如果采用单向链表当前节点只能按到后继或者前继节点这样是无法将前继节点指向后继节点的需要遍历整个 AQS从头或者从尾去找。单向链表在移除AQS中排队的Node时成本很高。 当前在唤醒后继节点时如果是单向链表也会出问题因为节点插入方式的问题导致只能单向的去找有效节点去唤醒从而造成很多次无效的遍历操作如果是双向链表就可以解决这个问题。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/909470.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!