写点什么

AQS 中那些不得不说的理论知识

  • 2022 年 5 月 06 日
  • 本文字数:2063 字

    阅读完需:约 7 分钟

2.2 next

同步队列的后继节点,条件队列没有这个概念。

2.3 thread

竞争资源的线程。

2.4 nextWaiter

条件队列的下一个节点,同步队列没有这个概念。

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 改为SIGNA **《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】** L(-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()需要根据不同的业务场景,由不同实现类实现,

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
AQS中那些不得不说的理论知识_Java_爱好编程进阶_InfoQ写作社区