🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论
🔥🔥🔥(源码获取 + 调试运行 + 问题答疑)🔥🔥🔥 有兴趣可以联系我
🔥🔥🔥 文末有往期免费源码,直接领取获取(无删减,无套路)
我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。
1. 共享模式与独占模式的根本差异
在Java并发编程中,AbstractQueuedSynchronizer(AQS)提供了两种基本的同步模式:独占模式(Exclusive)和共享模式(Shared)。这两种模式在节点入队和出队操作上存在着本质性的区别,理解这些差异是掌握AQS高级用法的关键。
独占模式(如ReentrantLock)的特点是同一时刻只允许一个线程访问共享资源,获取锁和释放锁的操作都是针对单个线程的。与之形成鲜明对比的是,共享模式(如Semaphore、CountDownLatch)允许多个线程同时访问资源,这种"共享"特性在节点出队时引发了独特的"传播"机制。
从语义层面来看,独占模式体现的是"互斥"思想,而共享模式体现的是"协作"思想。这种根本差异直接反映在AQS的节点状态管理和唤醒策略上。
2. 共享节点的入队操作:与独占模式的对比分析
2.1 基本入队流程的相似性
在节点入队的基本流程上,共享模式与独占模式都遵循相同的enq方法:
private Node enq(Node node) {
for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
这个自旋CAS操作是两种模式共用的基础设施,体现了AQS设计上的复用思想。无论是共享节点还是独占节点,都需要通过这个安全的入队机制加入到同步队列中。
2.2 模式标识的关键差异
真正的差异体现在节点的模式标识上。在AQS的Node类中,通过nextWaiter字段来区分节点模式:
-
独占节点:nextWaiter指向一个特殊的EXCLUSIVE标记
-
共享节点:nextWaiter指向一个特殊的SHARED标记
这个标识在节点创建时确定,并在整个生命周期中保持不变。它决定了节点在同步队列中的行为方式,特别是在出队时的唤醒策略。
3. setHeadAndPropagate:共享模式出队的核心引擎
3.1 方法执行的双重职责
setHeadAndPropagate方法是共享模式最核心的出队操作方法,它承担着两个重要职责:
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
职责一:更新头节点 通过setHead(node)将当前节点设置为新的头节点,同时清空节点的线程引用,帮助垃圾回收。这个操作是线程安全的,因为只有成功获取资源的线程才能执行这个方法。
职责二:判断传播条件 方法通过多个条件判断来决定是否继续传播唤醒信号,这些条件构成了共享模式唤醒传播的决策逻辑。
3.2 传播条件的精细判断
传播条件的判断体现了AQS在性能优化上的精细考量:
-
propagate > 0:明确指示需要传播(如Semaphore的许可证还有剩余)
-
原头节点状态检查:h.waitStatus < 0 表示原头节点可能处于SIGNAL或PROPAGATE状态
-
新头节点状态检查:再次检查当前头节点状态,应对并发场景
这种多层次的条件检查确保了在复杂的并发环境下,唤醒信号既不会丢失,也不会过度传播。
4. PROPAGATE状态:传播机制的关键使者
4.1 PROPAGATE状态的引入背景
在早期的AQS实现中,存在一个已知的竞争条件问题:当某个线程释放资源并准备唤醒后继节点时,另一个线程可能刚好在此时获取资源并修改了状态,导致唤醒信号丢失。
PROPAGATE状态(值为-3)就是为了解决这个问题而引入的。它作为一个中间状态,表示"应该继续传播唤醒信号"。
4.2 PROPAGATE状态的工作机制
PROPAGATE状态在doReleaseShared方法中发挥关键作用:
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h);
} else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)
break;
}
}
当头节点的waitStatus为0时,doReleaseShared会尝试将其设置为PROPAGATE。这个状态告诉其他线程:"这里刚刚发生了一次释放操作,可能需要继续传播唤醒"。
5. 传播唤醒的连锁反应
5.1 唤醒信号的级联传递
共享模式最精妙的特点在于唤醒信号的"连锁反应"。当一个共享节点成功获取资源后,它会检查后续节点:
-
如果后续节点也是共享模式,立即调用doReleaseShared()
-
doReleaseShared会唤醒后续节点,使其尝试获取资源
-
被唤醒的节点在成功获取资源后,重复这个过程
这种机制形成了唤醒信号的级联传递,使得多个等待的共享节点可以快速、批量地被激活。
5.2 性能优势的具体体现
与独占模式相比,共享模式的传播机制带来了显著的性能优势:
减少不必要的挂起和唤醒 在独占模式下,每个线程需要单独被唤醒、尝试获取锁、可能再次挂起。而在共享模式下,一次成功的获取可能触发一连串的唤醒,大大减少了线程状态切换的开销。
提高资源利用率 对于像Semaphore这样的资源控制器,传播机制确保了可用资源被快速分配给等待的线程,减少了资源空闲时间。
降低系统调用开销 每次LockSupport.unpark()都涉及系统调用,批量唤醒减少了这类开销,提高了整体吞吐量。
6. 实际应用场景分析
6.1 Semaphore中的传播实现
以Semaphore为例,在非公平模式下,tryAcquireShared方法返回剩余许可证数量:
protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 || compareAndSetState(available, remaining))
return remaining;
}
}
当remaining > 0时,setHeadAndPropagate会继续传播唤醒信号,确保所有可用的许可证都被快速分配。
6.2 CountDownLatch的一次性传播
CountDownLatch在状态达到0时,会触发一次大规模的传播:
public void countDown() {
sync.releaseShared(1);
}
protected int tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
当计数器归零时,所有等待的线程会通过传播机制被同时唤醒,这正是CountDownLatch"同时启程"语义的实现基础。
7. 并发环境下的正确性保证
7.1 竞争条件的处理
传播机制在面临并发访问时,通过多种策略保证正确性:
CAS操作的原子性 所有状态变更都通过CAS操作完成,确保在并发环境下不会出现状态不一致。
重试机制的容错性 doReleaseShared中的无限循环配合CAS失败后的重试,提供了良好的容错能力。
状态检查的保守性 setHeadAndPropagate中的条件判断倾向于"过度传播"而非"丢失传播",这种保守策略在实践中更为安全。
7.2 性能与正确性的平衡
AQS在传播机制的设计上体现了性能与正确性的精妙平衡:
-
通过PROPAGATE状态避免信号丢失
-
通过条件判断避免过度传播
-
通过级联唤醒提高吞吐量
-
通过状态检查保证安全性
8. 最佳实践与性能调优
8.1 自定义同步器的实现建议
在基于AQS实现自定义共享同步器时,应该:
-
准确返回传播信号:tryAcquireShared方法应该准确返回剩余资源数量
-
合理设置状态:在tryReleaseShared中正确判断是否需要触发传播
-
考虑边界条件:特别是资源数量为0和整数溢出的情况
8.2 性能监控与诊断
在实际生产环境中,监控共享同步器的性能表现很重要:
-
观察线程的等待时间和唤醒频率
-
监控同步队列的长度变化
-
分析传播链的长度和效果
总结
共享模式下的节点入队和出队操作通过精妙的传播机制,实现了多个线程的高效协作。PROPAGATE状态作为这个机制的核心,解决了并发环境下的竞争条件问题,同时保证了高性能和高吞吐量。
理解这种传播机制不仅有助于更好地使用Java并发工具类,也为设计和实现高性能的并发组件提供了宝贵的思路。在当今多核处理器成为主流的时代,这种共享协作的模式比传统的互斥锁更加符合现代系统的性能需求。


「在线考试系统源码(含搭建教程)」 (无删减,无套路):🔥🔥🔥
链接:https://pan.quark.cn/s/96c4f00fdb43 提取码:WR6M
往期免费源码对应视频:
免费获取--SpringBoot+Vue宠物商城网站系统
🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论
🔥🔥🔥(源码 + 调试运行 + 问题答疑)
🔥🔥🔥 有兴趣可以联系我
💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!💖常来我家多看看,
📕网址:扣棣编程,
🎉感谢支持常陪伴,
🔥点赞关注别忘记!💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!
⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇点击此处获取源码⬇⬇⬇⬇⬇⬇⬇⬇⬇

836

被折叠的 条评论
为什么被折叠?



