2.5 waitStatus

队列中节点的等待状态。

  • static final int CANCELLED = 1; 此节点的线程被取消 独占模式 共享模式

  • static final int SIGNAL = -1; 此节点的后继节点线程被挂起,需要被唤醒 独占模式

  • static final int CONDITION = -2; 此节点的线程在等待信号,也表明当前节点不在同步队列中,而在条件队列中

  • static final int PROPAGATE = -3; 此节点下一个acquireShared应该无条件传播   共享模式

这四个属性就是waitStatus属性的具体状态,还有一个隐式的具体状态,即waitStatus初始化时为0。在独占模式下,我们只需要用到CANCELLED和SIGNAL,这里需要注意的是SIGNAL,它代表的不是自己线程的状态,而是它后继节点的状态,当一个节点的waitStatus被置为SIGNAL时,表明此节点的后继节点被挂起,当此节点释放锁或被取消放弃拿锁时,应该唤醒后继节点。而在共享模式时,我们会用到CANCELLED和PROPAGATE

3、ConditionObject

是AQS的内部类ConditionObject。

3.1 firstWaiter

条件队列的第一个节点。

3.2 lastWaiter

条件队列的最后一个节点。

3.2 await()

3.2.1 创建一个条件队列节点,把自己加入到条件队列中,必要的时候初始化条件队列;

3.2.2 因为调用await的线程都持有锁,所以接下来需要执行AQS的release方法释放当前线程持有的锁,即让出锁,让其他线程执行;

3.2.3 利用park方法将当前线程挂起,等待唤醒;

3.2.4 其他线程调用signal()方法唤醒该线程(该方法是隐式的,在代码中没有体现,因为是多线程执行,体现在signal())

  • 将节点从条件队列转移到等待队列

  • 调用unpark方法唤醒线程

  • 重新竞争锁。这个步骤也很关键,这里可以区分公平锁和非公平锁

3.3 signal()

3.3.1 将条件队列中的的节点(firstWaiter)转移到同步队列中

3.3.2 把刚转移到同步队列中的节点前驱的waitstatus改为SIGNAL(-1),用来唤醒后继节点。

4、队列同步器模式

4.1 共享模式

同一时间只有一个线程能拿到锁执行。

4.2 独占模式

同一时间有多个线程可以拿到锁协同工作。

4.3 公平锁

多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

4.4 非公平锁

多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

4.5 可重入锁

广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁。

在ReentrantLock上的体现:

final boolean nonfairTryAcquire(int acquires) {  //这里我就跳过着讲解了,ReentrantLock模式使用的是非公平锁,这样能提高系统的响应性能

final Thread current = Thread.currentThread();

int c = getState();  //获取资源的状态,

if (c == 0) {     //为0就是别人还没有获取到锁,这个时候当前线程就可以获取到锁

if (compareAndSetState(0, acquires)) { //用cas的方式获取到锁

setExclusiveOwnerThread(current);  //这个方法里面就只有这一句  exclusiveOwnerThread = thread; 设置当前线程是独占线程

return true;

}

}

else if (current == getExclusiveOwnerThread()) {   //重点来了,这个方法就是主要判断是不是可重入的,如果之前的判断资源的状态是被上锁了,就会执行到这里,如果判断是本线程

int nextc = c + acquires;  //把资源的的请求次数加1

if (nextc < 0) // 当然也不是可以不限加的,如果超出的int的范围,抛出一个error的错误

throw new Error(“Maximum lock count exceeded”);

setState(nextc);  //设置资源状态

return true;

}

return false;

}

总结:ReentrantLock可重入主要体现在current == getExclusiveOwnerThread()这个判断方法上面。如果是当前重入线程,资源状态添加请求数,注意释放的时候也是要释放这个多次的。

5、acquire(int arg) 线程竞争锁

竞争锁方法的路径tryAcquire()->addWaiter()->acquireQueued()->selfInterrupt(),其中tryAcquire()需要根据不同的业务场景,由不同实现类实现。由于是多线程模式,该方法使用了很多for (;;)来确保业务一定执行,具体可以参考源码。对应下面四个步骤:

5.1、竞争锁。竞争成功终止操作,竞争失败执行以下步骤;

5.2、将线程包装成独占式节点,并入队,放到队尾;

5.3、 当前线程加入等待队列后,会通过acquireQueued方法基于CAS自旋不断尝试获取资源,直至获取到资源竞争成功移除该节点并终止操作;

竞争失败修改前驱节点waitstatus=SIGNAL(-1),则会调用park阻塞线程。

5.4、 自我中断锁(用户线程第一次获取锁失败之后,进入CLH队列,此时会中断该线程)。

6、release(int arg) 线程释放锁

释放锁的路径tryRelease()->unparkSuccessor(),其中tryAcquire()需要根据不同的业务场景,由不同实现类实现,

6.1 由于模板模式,不同的子类实现不同,一般是修改锁状态;

6.2 获取等待队列的头节点,调用unpark()唤醒后继节点的线程。

7、为什么唤醒后继节点中,是从后向前遍历?

因为cancelAcquire方法的处理过程中只设置了next的变化,没有设置prev的变化,在最后有这样一行代码:node.next = node。并发情况下从前向后遍历的话,可能就死循环了,所以这时只有prev是稳定的。

唤醒后继节点的线程后,被唤醒的线程在parkAndCheckInterrupt方法,返回线程在park过程中是否被用户中断过,然后到acquireQueued方法中,如果该节点的前驱节点是头节点,则尝试获取资源,成功获取资源后,将是否被中断标识返回acquire方法,如果被中断过,那此时中断。

如果被唤醒的线程所在节点的前继节点不是头结点,经过shouldParkAfterFailedAcquire的调整(清除无效(waitState=CANCELLED)的节点),也会移动到等待队列的前面,直到其前继节点为头结点。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)

img

最后

终极手撕架构师的学习笔记:分布式+微服务+开源框架+性能优化

image

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
.csdnimg.cn/images/e5c14a7895254671a72faed303032d36.jpg" alt=“img” style=“zoom: 33%;” />

最后

终极手撕架构师的学习笔记:分布式+微服务+开源框架+性能优化

[外链图片转存中…(img-KXwG6sQh-1711580009518)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐