共享模式
共享模式允许一组线程获取同一个许可。为实现共享模式子类需要实现两个方法:
- tryAcquireShared:返回int类型的值,小于0表示获取失败,等于0表示获取成功但不允许后续更多的获取,大于0表示获取成功且允许更多的后续获取。
- tryReleaseShared:返回true表示释放许可成功,可以唤醒等待线程;false表示失败,不唤醒等待线程。
共享获取 acquireShared
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
// 添加到等待队列,不管是共享模式还是独占模式,都共享同一个等待队列。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg); // 尝试获取,返回值表示是否允许获取
if (r >= 0) {
// 获取成功
// 把自己设为头结点并传递可以获取的信号
// node 把自己设为头结点后,它的后继发现它的前驱是头结点了,就会尝试获取。
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* 尝试通知队列里的下一个结点,如果:
* 调用者指示或者之前操作记录显示需要传递
* (注意:这里对waitStatus使用单一检查,因为PROPAGATE可能被转换到SIGNAL)
* 并且
* 下一个结点以共享模式等待或者我们根本就不知道,因为它是空的。
*
* 在这些检查有点保守,可能导致不必要的唤醒,但只是在多重竞争acquires/releases时,
* 因此,大多数都是现在或不久就需要通知的。
*/
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
private void setHead(Node node) {
head = node;
node.thread = null; // for GC
node.prev = null;
}
共享释放 releaseShared
释放共享许可的时候,最重要的是保证传递唤醒。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true ;
}
return false;
}
// 释放共享的核心方法
private void doReleaseShared() {
// 要确保release传递,即使有其他正在进行的acquires/releases。
// 这个过程的一般做法是尝试unpark head的后继,如果它(head)需要信号。
// 如果head不需要信号,把状态设为PROPAGATE来确保一旦release,传递可以继续。
// 另外,我们必须在循环里做这个,以免有新节点添加进来。
// 不像unparkSuccessor的其他使用,我们需要知道CAS重置状态失败与否,如果失败,则重新检测。
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 头结点
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {
continue; // 头结点状态被改变,需要重新检测。loop to recheck cases
}
// CAS成功说明h结点需要通知后继,唤醒
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
// 如果是初始头结点,把其状态设为PROPAGATE,确保传递继续
// 状态更改失败,需要再次检测。
continue; // loop on failed CAS
}
}
if (h == head) { // loop if head changed
// 如果操作过程中,头结点被改变(可能新增结点或者一个被唤醒线程把自己设为头结点了),需要再次检测。
break;
}
}
}
CountDownLatch
作用:在完成一组正在其他线程中执行的操作之前,CountDownLatch允许一个或多个线程一直等待。CountDownLatch只阻塞一次,倒数到0之后,调用await方法的将直接通过。
看看实现阻塞与放行的内部类:
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync( int count) {
setState(count); // 需要countDown的次数
}
int getCount() {
return getState();
}
protected int tryAcquireShared( int acquires) {
// 如果倒数到0,返回1表示允许后续获取,这样可以让AQS框架通知后继,
// 否则返回-1表示失败,不能获取,线程会进入等待。
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared( int releases) {
// Decrement count; signal when transition to zero
for (;;) { // CountDownLatch.countDown()可能被多线程调用,需要失败后重试
int c = getState();
if (c == 0)
return false ;
// 注意:这里是减1,而不是减去releases,因为CountDownLatch是对countDown调用次数的计数
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
代码很简洁,因为AQS框架确实非常强大。
CountDownLatch类的一些方法:
public CountDownLatch( int count) {
if (count < 0) throw new IllegalArgumentException( "count < 0");
this.sync = new Sync(count);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await( long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void countDown() {
sync.releaseShared(1);
}
相关推荐
ReentrantLock Lock 加锁过程源码分析图,AQS 源码分析
网盘文件永久链接
juc源码视频教程最全
集合源码分析 高并发与多线程 Stargazers over time 线程 线程的创建和启动 线程的sleep、yield、join 线程的状态 代码在 部分。 synchronized关键字(悲观锁) synchronized(Object) 不能用String常量、Integer、Long...
juc并发编程脑图以及相关示例代码
尚硅谷_JUC线程高级_源码、课件 ·1. 尚硅谷_JUC线程高级_volatile 关键字与内存可见性 ·2. 尚硅谷_JUC线程高级_原子变量与 CAS 算法 ·3. 尚硅谷_JUC线程高级_模拟 CAS 算法 ·4. 尚硅谷_JUC线程高级_同步容器类...
【尚硅谷】大厂必备技术之JUC并发编程视频 配套资料,自己根据视频整理 pdf 课件,和代码 视频地址:...
1、根据尚硅谷JUC并发编程(对标阿里P6-P7)视频自己整理的pdf文档 2、包含源码 视频地址:https://www.bilibili.com/video/BV1ar4y1x727/?p=1&vd_source=c634d163b940964d44747b4c3976117b 参考资料:...
从浅入深学习并发编程
JUC是什么 线程 进程 / 线程 线程状态 wait / sleep 并发 / 并行 Lock 使用Lock锁 可重入锁 公平锁 / 非公平锁 Synchronized / Lock 线程通讯 wait()、notify()和notifyAll() 虚假唤醒 Condition 定制化通信 多线程...
AQS源码分析一、锁的介绍1.1 乐观锁/悲观锁1.2 共享锁/独占锁1.3 公平锁/非公平锁1.4 小结二、AQS框架结构介绍2.1 类图2.2 AQS数据结构三、源码详解3.1 acquire源码详解3.2 release源码详解四、从ReentranLock看公平...
juc详解juc详解juc详解juc详解juc详解
JUC学习样例源码
MySQL、JVM、RocketMQ、JUC、设计模式、数据结构与算法学习总结
自己整理的多线程JUC笔记尚硅谷JUC视频笔记整理,很详细和全面,帮你迅速掌握JUC
JUC提供了一些常用的并发编程模式和工具,如线程池、并发集合、原子操作等。 JUC的主要特点包括: 线程池:JUC提供了Executor框架,可以方便地创建和管理线程池,实现任务的异步执行和线程的复用。 并发集合:JUC...
JUC线程锁框架深度解析,包含整套视频和源码以及开发文档。
JUC底层实现原理分析,结构多线程更加贯彻同步知识
狂神说JUC代码
个人整理的JUC知识总结