共享模式
共享模式允许一组线程获取同一个许可。为实现共享模式子类需要实现两个方法:
- 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);
}
相关推荐
《JUC并发编程与源码分析视频课》是一门深入探讨Java并发编程的课程,主要聚焦于Java Util Concurrency(JUC)库的使用和源码解析。JUC是Java平台提供的一组高级并发工具包,它极大地简化了多线程编程,并提供了更...
本教程将深入探讨JUC源码,旨在帮助你全面理解其背后的实现原理。 在Java中,JUC(java.util.concurrent)包包含了多种并发控制和同步组件,如线程池、锁、原子变量、并发容器等。这些组件设计精巧,性能优秀,能够...
ReentrantLock Lock 加锁过程源码分析图,AQS 源码分析
集合源码分析 高并发与多线程 Stargazers over time 线程 线程的创建和启动 线程的sleep、yield、join 线程的状态 代码在 部分。 synchronized关键字(悲观锁) synchronized(Object) 不能用String常量、Integer、Long...
juc并发编程脑图以及相关示例代码
Java 并发库(Java Util Concurrency, JUC)是 Java 平台中用于处理并发问题的核心工具包,提供了丰富的类和接口来简化并发编程。 在计算机系统中,进程是操作系统资源分配的基本单位,它拥有独立的内存空间,而...
尚硅谷_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_atguigu`这个压缩包中,包含了上述组件的示例代码,通过实际运行和分析这些代码,你可以深入理解JUC的工作原理和使用场景。同时,这些示例也能帮助你学习如何在实际项目中有效地利用JUC来提高并发程序的性能...
JUC(Java Util Concurrent),即Java的并发工具包,是Java提供的一套并发编程解决方案,它通过一系列接口和类简化了并发编程的复杂性。本笔记整理涉及了JUC的内存可见性、volatile关键字以及CAS算法和原子变量等多...
在这个深度解析JUC线程锁框架的主题中,我们将探讨其核心组件、设计模式以及如何在实际应用中有效利用。 1. **原子变量(Atomic Variables)** JUC提供了一系列的原子变量类,如AtomicInteger、AtomicLong等,它们...
JUC是什么 线程 进程 / 线程 线程状态 wait / sleep 并发 / 并行 Lock 使用Lock锁 可重入锁 公平锁 / 非公平锁 Synchronized / Lock 线程通讯 wait()、notify()和notifyAll() 虚假唤醒 Condition 定制化通信 多线程...
Java并发编程库(JUC)是Java标准库的一部分,提供了丰富的线程和同步工具,如Semaphore、CyclicBarrier、CountDownLatch和ExecutorService。理解并发原理,如线程安全、死锁和活锁,以及如何使用这些工具来实现高效...
AQS源码分析一、锁的介绍1.1 乐观锁/悲观锁1.2 共享锁/独占锁1.3 公平锁/非公平锁1.4 小结二、AQS框架结构介绍2.1 类图2.2 AQS数据结构三、源码详解3.1 acquire源码详解3.2 release源码详解四、从ReentranLock看公平...
根据提供的文件信息,“尚硅谷JUC百度云连接”这一标题和描述主要指向的是尚硅谷教育机构所提供的关于Java并发编程(Java Util Concurrency,简称JUC)的学习资源,并且通过一个百度网盘链接来分享这些资源。...
Java并发编程领域中的JUC(Java Util Concurrency)是一门深奥且实用的技术,它包含在Java的`java.util.concurrent`包中,为多线程编程提供了高效、易用的工具。这个压缩包文件“个人学习JUC代码笔记总集”显然是一...
JUC(java.util.concurrent)是Java提供的一个并发编程工具包...要掌握JUC并发编程及其底层原理,除了通过阅读官方文档和源码学习外,还需要大量实践和经验积累,才能够真正理解和应用JUC中的并发工具来解决实际问题。
它提供了实现排他模式(独占式)和共享模式锁的机制,被广泛应用于Java并发包中的ReentrantLock、Semaphore、CountDownLatch等组件中。 首先,AQS是一个抽象类,它的主要特点是使用一个int类型的变量(state)表示...
为了解决这些问题,并进一步提高多线程程序的编写效率与可维护性,Java 5引入了JUC(Java Util Concurrency)包。JUC包提供了大量高级并发工具类,这些工具类简化了多线程编程的难度,使得开发者能够更加专注于业务...