Java等待通知机制比较(wait,notify,notifyAll;CountDownLatch;Condition)

本文深入探讨了Java多线程中的线程协作机制,包括wait、notify和notifyAll的基本使用,强调了它们必须在同步环境中调用。接着分析了CountDownLatch的原理,它是通过AQS同步器实现,用于让一个线程等待其他线程完成任务。最后讨论了Condition接口,阐述了signal和signalAll如何唤醒等待队列中的线程参与锁的竞争。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

@[TOC](wait,notify,notifyAll;CountDownLatch;Condition原理和解析)
#在Java多线程开发中,涉及到线程之间的协作,配合。为了满足不同场景的开发需求,Java也提供了多种方式供开发人员选择。

part1:wait,notify,notifyAll

首先可以看一下这三个方法的源码位置,
在这里插入图片描述
在这里插入图片描述
从源码我们可以知道,这三个方法都是java的最顶层的类Object的方法,并且是native方法,也就是说着三个方法执行时需要对象级别的锁,在调用 wait () 、notify() 系列方法 之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait ()方法 、notify() 系列方法
标准模式:
1)获取对象的锁。
2)如果条件不满足,那么调用对象的 wait()方法,被通知后仍要检查条件。
3)条件满足则执行对应的逻辑。
在这里插入图片描述
通知方遵循如下原则。
1)获得对象的锁。
2)改变条件。
3)通知所有等待在对象上的线程。
在这里插入图片描述
notify和notifyAll应该使用谁?
尽可能用 notifyall(),谨慎使用 notify(),因为 notify()只会唤醒一个线程,我
们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程

【问】: yield() 、sleep()、wait()、notify()等方法对锁的影响?
yield() 、sleep()被调用后,都不会释放当前线程所持有的锁。
调用 wait()方法后,会释放当前线程持有的锁,而且当前被唤醒后,会重新
去竞争锁,锁竞争到后才会执行 wait 方法后面的代码。
调用 notify()系列方法后,对锁无影响,线程只有在 syn 同步代码执行完后才
会自然而然的释放锁,所以 notify()系列方法一般都是 syn 同步代码的最后一行。

part2:CountDownLatch(闭锁)原理分析

我们都知道CountDownLatch在多线程 中使用是比较频繁的,能够使一个线程等待其他线程完成各自的工作后再执行
说明:
1.CountDownLatch 是通过一个计数器来实现的,计数器的初始值为初始任务
的数量。每当完成了一个任务后,计数器的值就会减 1
(CountDownLatch.countDown()方法)。当计数器值到达 0 时,它表示所有的已
经完成了任务,然后在闭锁上等待 CountDownLatch.await()方法的线程就可以恢
复执行任务
2.底层实现可以通过源码来了解具体实现
首先
在这里插入图片描述
查看源码,可以知道,内部是通过AQS同步器来构建锁,要理解CountDownLatch的工作原理,首先了解一下AQS同步器:
队列同步器 AbstractQueuedSynchronizer 简称同步器或 AQS,是用
来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状
态,通过内置的 FIFO 队列来完成资源获取线程的排队工作;
AQS 的主要使用方式是继承,子类通过继承 AQS 并实现它的抽象方法来管
理同步状态,在 AQS 里由一个 int 型的 state 来代表这个状态,在抽象方法的实
现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的 3 个方法
(getState()、setState(int newState)和 compareAndSetState(int expect,int update))
来进行操作,因为它们能够保证状态的改变是安全的
在这里插入图片描述
在实现上,子类推荐被定义为自定义同步组件的静态内部类,AQS 自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态,这样就可以方便实现不同类型的同步组件(ReentrantLock、ReentrantReadWriteLock 和 CountDownLatch 等)
同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器。可以这样理解二者之间的关系:锁是面向使用者的,它定义了使用者与锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节;
同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。锁和同步器很好地隔离了使用者和实现者所需关注的领域。
实现者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。----》这一点在CountDownLatch 的上图的源码中可以很好的体现,Sync就是CountDownLatch 中的静态内部类,并且继承AQS,实现了自己的获取锁,释放锁的功能
CountDownLatch 主要应用场景:实现最大的并行性,有时我们想同时启动多个线程,实现最大程度的并行性
在这里插入图片描述

part3 Condition

 Condition是一个接口,配合lock接口可以实现类似于 Object上的 wait()、wait(long timeout)、notify()以及 notifyAll()方法配合synchronized的等待/通知效果;
 通过源码可以了解到Condition的实现是在AQS中的内部类实现的

在这里插入图片描述
/**
* Moves the longest-waiting thread, if one exists, from the
* wait queue for this condition to the wait queue for the
* owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}

/**
* Moves all threads from the wait queue for this condition to
* the wait queue for the owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}

/**
* Implements interruptible condition wait.
*


  1. *
  2. If current thread is interrupted, throw InterruptedException.
    *
  3. Save lock state returned by {@link #getState}.
    *
  4. Invoke {@link #release} with saved state as argument,
    * throwing IllegalMonitorStateException if it fails.
    *
  5. Block until signalled or interrupted.
    *
  6. Reacquire by invoking specialized version of
    * {@link #acquire} with saved state as argument.
    *
  7. If interrupted while blocked in step 4, throw InterruptedException.
    *

*/
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
所以Condition一般是配合继承AQS的LOCK(ReentrantLock,ReentrantReadWriteLock等)进行使用。
通过源码可以知道一个Condition包含一个等待队列,
在这里插入图片描述
AQS的同步队列和等待队列关系(一个lock可以有多个等待队列,所以,我们在唤醒线程的时候,应该尽量根据Condition唤醒某一个线程(尽量使用signal)):
在这里插入图片描述

当调用await()的时候,会把当前线程从同步队列加入到等待队列中
在这里插入图片描述
当调用signal() ,signalAll() 时,会把等待队列的线程加入到同步队列中,从而参与锁的竞争,进而唤醒线程。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值