Java:notify() 与 notifyAll() 重头再来

2024-02-17

如果一副护目镜“之间的差异notify() and notifyAll()”然后会弹出很多解释(抛开javadoc段落)。这一切都归结为被唤醒的等待线程的数量:一notify()和所有在notifyAll().

然而(如果我确实理解这些方法之间的区别的话),总是只选择一个线程来进一步获取监视器;在第一种情况下,由VM选择,在第二种情况下,由系统线程调度程序选择。程序员并不知道两者的确切选择过程(在一般情况下)。

什么是useful之间的区别notify() http://download.oracle.com/javase/6/docs/api/java/lang/Object.html#notify%28%29 and 通知全部() http://download.oracle.com/javase/6/docs/api/java/lang/Object.html#notifyAll%28%29然后?我错过了什么吗?


清楚地,notify唤醒等待集中的(任何)一个线程,notifyAll唤醒等待集中的所有线程。下面的讨论应该可以消除任何疑问。notifyAll大多数时候应该使用。如果您不确定使用哪个,请使用notifyAll.请参阅下面的解释。

非常仔细地阅读并理解。如果您有任何疑问,请给我发电子邮件。

查看生产者/消费者(假设是一个具有两个方法的 ProducerConsumer 类)。它坏了(因为它使用notify) - 是的,它可能有效 - 即使在大多数情况下,但它也可能导致死锁 - 我们会看到原因:

public synchronized void put(Object o) {
    while (buf.size()==MAX_SIZE) {
        wait(); // called if the buffer is full (try/catch removed for brevity)
    }
    buf.add(o);
    notify(); // called in case there are any getters or putters waiting
}

public synchronized Object get() {
    // Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
    while (buf.size()==0) {
        wait(); // called if the buffer is empty (try/catch removed for brevity)
        // X: this is where C1 tries to re-acquire the lock (see below)
    }
    Object o = buf.remove(0);
    notify(); // called if there are any getters or putters waiting
    return o;
}

FIRSTLY,

为什么我们需要一个 while 循环来围绕等待?

我们需要一个while循环以防我们遇到这种情况:

消费者1(C1)进入同步块并且缓冲区为空,因此C1被放入等待集中(通过wait称呼)。消费者2(C2)即将进入synchronized方法(上面Y点),但是生产者P1在缓冲区中放入了一个对象,随后调用notify。唯一等待的线程是 C1,因此它被唤醒,现在尝试重新获取 X 点(上图)处的对象锁。

现在C1和C2正在尝试获取同步锁。其中一个(不确定地)被选择并进入方法,另一个被阻塞(不是等待 - 而是阻塞,试图获取方法上的锁)。假设C2 首先获得锁。 C1 仍然处于阻塞状态(尝试获取 X 处的锁)。 C2 完成该方法并释放锁。现在,C1 获取了锁。你猜怎么着,幸运的是我们有一个while循环,因为 C1 执行循环检查(保护)并被阻止从缓冲区中删除不存在的元素(C2 已经得到了它!)。如果我们没有while,我们会得到一个IndexArrayOutOfBoundsException因为 C1 试图从缓冲区中删除第一个元素!

NOW,

好吧,现在为什么我们需要notifyAll?

在上面的生产者/消费者示例中,看起来我们可以逃脱notify。看起来是这样,因为我们可以证明,守卫在wait生产者和消费者的循环是互斥的。也就是说,看起来我们不能让线程在等待put方法以及get方法,因为要使这一点成立,则以下条件必须成立:

buf.size() == 0 AND buf.size() == MAX_SIZE(假设MAX_SIZE不为0)

然而,这还不够好,我们需要使用notifyAll。让我们看看为什么...

假设我们有一个大小为 1 的缓冲区(为了使示例易于理解)。以下步骤导致我们陷入僵局。请注意,任何时候线程被通知唤醒,它都可以由 JVM 不确定地选择 - 也就是说,任何等待的线程都可以被唤醒。另请注意,当多个线程在进入方法时阻塞(即尝试获取锁)时,获取的顺序可能是不确定的。还请记住,一个线程在任何时候只能位于其中一个方法中 - 同步方法只允许一个线程执行(即持有该类中的任何(同步)方法的锁)。如果发生以下事件序列 - 会导致死锁:

STEP 1:
- P1 将 1 个字符放入缓冲区

STEP 2:
- P2尝试put- 检查等待循环 - 已经是一个字符 - 等待

STEP 3:
- P3尝试put- 检查等待循环 - 已经是一个字符 - 等待

STEP 4:
- C1 尝试获取 1 个字符
- C2 尝试在进入时获取 1 个字符块get method
- C3 尝试在进入时获取 1 个字符块get method

STEP 5:
- C1 正在执行get方法 - 获取字符,调用notify, 退出方法
- The notify唤醒P2
- 但是,C2 在 P2 之前进入方法(P2 必须重新获取锁),因此 P2 在进入方法时阻塞put method
- C2 检查等待循环,缓冲区中没有更多字符,因此等待
- C3 在 C2 之后、P2 之前进入方法,检查等待循环,缓冲区中没有更多字符,因此等待

STEP 6:
- 现在:P3、C2 和 C3 正在等待!
- 最后P2获得锁,将一个字符放入缓冲区,调用notify,退出方法

STEP 7:
- P2的通知唤醒P3(记住任何线程都可以被唤醒)
- P3 检查等待循环条件,缓冲区中已经有一个字符,因此等待。
- 不再需要调用通知的线程,并且三个线程永久挂起!

解决方案:更换notify with notifyAll在生产者/消费者代码中(上面)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java:notify() 与 notifyAll() 重头再来 的相关文章

  • 使用 Android 发送 HTTP Post 请求

    我一直在尝试从 SO 和其他网站上的大量示例中学习 但我无法弄清楚为什么我编写的示例不起作用 我正在构建一个小型概念验证应用程序 它可以识别语音并将其 文本 作为 POST 请求发送到 node js 服务器 我已确认语音识别有效 并且服务
  • 在 HTTPResponse Android 中跟踪重定向

    我需要遵循 HTTPost 给我的重定向 当我发出 HTTP post 并尝试读取响应时 我得到重定向页面 html 我怎样才能解决这个问题 代码 public void parseDoc final HttpParams params n
  • JAXb、Hibernate 和 beans

    目前我正在开发一个使用 Spring Web 服务 hibernate 和 JAXb 的项目 1 我已经使用IDE hibernate代码生成 生成了hibernate bean 2 另外 我已经使用maven编译器生成了jaxb bean
  • 多个 Maven 配置文件激活多个 Spring 配置文件

    我想在 Maven 中构建一个环境 在其中我想根据哪些 Maven 配置文件处于活动状态来累积激活多个 spring 配置文件 目前我的 pom xml 的相关部分如下所示
  • JavaMail 只获取新邮件

    我想知道是否有一种方法可以在javamail中只获取新消息 例如 在初始加载时 获取收件箱中的所有消息并存储它们 然后 每当应用程序再次加载时 仅获取新消息 而不是再次重新加载它们 javamail 可以做到这一点吗 它是如何工作的 一些背
  • Mockito when().thenReturn 不必要地调用该方法

    我正在研究继承的代码 我编写了一个应该捕获 NullPointerException 的测试 因为它试图从 null 对象调用方法 Test expected NullPointerException class public void c
  • 斯坦福 NLP - 处理文件列表时 OpenIE 内存不足

    我正在尝试使用斯坦福 CoreNLP 中的 OpenIE 工具从多个文件中提取信息 当多个文件 而不是一个 传递到输入时 它会给出内存不足错误 All files have been queued awaiting termination
  • 从 127.0.0.1 到 2130706433,然后再返回

    使用标准 Java 库 从 IPV4 地址的点分字符串表示形式获取的最快方法是什么 127 0 0 1 到等效的整数表示 2130706433 相应地 反转所述操作的最快方法是什么 从整数开始2130706433到字符串表示形式 127 0
  • Java TestNG 与跨多个测试的数据驱动测试

    我正在电子商务平台中测试一系列商店 每个商店都有一系列属性 我正在考虑对其进行自动化测试 是否有可能有一个数据提供者在整个测试套件中提供数据 而不仅仅是 TestNG 中的测试 我尝试不使用 testNG xml 文件作为机制 因为这些属性
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • IO 密集型任务中的 Python 多线程

    建议仅在 IO 密集型任务中使用 Python 多线程 因为 Python 有一个全局解释器锁 GIL 只允许一个线程持有 Python 解释器的控制权 然而 多线程对于 IO 密集型操作有意义吗 https stackoverflow c
  • 加密 JBoss 配置中的敏感信息

    JBoss 中的标准数据源配置要求数据库用户的用户名和密码位于 xxx ds xml 文件中 如果我将数据源定义为 c3p0 mbean 我会遇到同样的问题 是否有标准方法来加密用户和密码 保存密钥的好地方是什么 这当然也与 tomcat
  • 如何在控制器、服务和存储库模式中使用 DTO

    我正在遵循控制器 服务和存储库模式 我只是想知道 DTO 在哪里出现 控制器应该只接收 DTO 吗 我的理解是您不希望外界了解底层域模型 从领域模型到 DTO 的转换应该发生在控制器层还是服务层 在今天使用 Spring MVC 和交互式
  • Google App Engine 如何预编译 Java?

    App Engine 对应用程序的 Java 字节码使用 预编译 过程 以增强应用程序在 Java 运行时环境中的性能 预编译代码的功能与原始字节码相同 有没有详细的信息这是做什么的 我在一个中找到了这个谷歌群组消息 http groups
  • 无法捆绑适用于 Mac 的 Java 应用程序 1.8

    我正在尝试将我的 Java 应用程序导出到 Mac 该应用程序基于编译器合规级别 1 7 我尝试了不同的方法来捆绑应用程序 1 日食 我可以用来在 Eclipse 上导出的最新 JVM 版本是 1 6 2 马文 看来Maven上也存在同样的
  • Java列表的线程安全

    我有一个列表 它将在线程安全上下文或非线程安全上下文中使用 究竟会是哪一个 无法提前确定 在这种特殊情况下 每当列表进入非线程安全上下文时 我都会使用它来包装它 Collections synchronizedList 但如果不进入非线程安
  • 在 Maven 依赖项中指定 jar 和 test-jar 类型

    我有一个名为 commons 的项目 其中包含运行时和测试的常见内容 在主项目中 我添加了公共资源的依赖项
  • 使用 JMF 创建 RTP 流时出现问题

    我正处于一个项目的早期阶段 需要使用 RTP 广播DataStream创建自MediaLocation 我正在遵循一些示例代码 该代码目前在rptManager initalize localAddress 出现错误 无法打开本地数据端口
  • C# - OutOfMemoryException 在 JSON 文件上保存列表

    我正在尝试保存压力图的流数据 基本上我有一个压力矩阵定义为 double pressureMatrix new double e Data GetLength 0 e Data GetLength 1 基本上 我得到了其中之一pressur
  • 使用 xpath 和 vtd-xml 以字符串形式获取元素的子节点和文本

    这是我的 XML 的一部分

随机推荐