AQS 中那些不得不说的理论知识
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()需要根据不同的业务场景,由不同实现类实现,
评论