【问题标题】:Why to use wait method on strings or other objects?为什么要对字符串或其他对象使用等待方法?
【发布时间】:2014-08-13 20:42:07
【问题描述】:

我有以下代码:

synchronized void myMethod() {
  String s="aaa";
  try {
    s.wait();
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
}

代码抛出异常... 我已经看到在线程上使用 wait 方法的代码,这是可以自我解释和合乎逻辑的..

  1. 为什么要在像字符串这样的对象上使用等待方法而不是在主线程上使用它?
  2. 它有什么用?
  3. 有没有这样的实际实现?

提前致谢

【问题讨论】:

  • 有什么异常?
  • @Miserable 变量:java.lang.IllegalMonitorStateException
  • 始终在检查等待条件的循环中使用wait,并与notify(All)结合使用。所有其他用法可能都是错误的。见docs.oracle.com/javase/tutorial/essential/concurrency/…
  • @biziclop 你不相信引导性问题,是吗?

标签: java multithreading wait


【解决方案1】:

您的示例代码将不起作用,因为该方法在调用 myMethod 的实例上同步,而在字符串上调用 wait。这将导致IllegalMonitorStateException你必须在你锁定的同一个对象上调用 wait 和 notify。 得到通知的线程是等待调用 notify 的锁的线程。

锁定字符串对象是个坏主意,不要这样做。您不想锁定无法推断谁能获得它们的事物,因为任何人 都可以获得它们。应用程序中其他地方的一些其他代码可能会锁定相同的字符串值,并且您可能会发生奇怪的交互、死锁,因为其他代码正在获取您的锁定,或者让其他代码通知您。在调试某些多线程行为时,您是否需要考虑字符串是如何池化的?

您可以通过定义自己的锁并将其设为私有来限制谁可以获取您的锁,如下所示:

private final Object LOCK = new Object();

所以只有调用您控制访问的对象的方法的线程才能获取锁:

public void myMethod() {
   synchronized(LOCK) {
      ...
   }
}

这样您就可以确切地知道什么可以获取锁,它并不是对应用程序中的每个线程都可用。任何可以获取该对象引用的东西都可以获取锁,因此请保持引用私有。

您的示例使用等待而不使用带有条件变量的循环的方式非常值得怀疑。线程可以在没有得到通知的情况下退出等待调用。即使通知了一个线程,调度程序也不会给它任何特殊的优先级。在线程被通知到线程可以重新获得它开始等待时放弃的锁的时间之间,另一个线程可以插入并做一些事情,可能会影响通知正在警告等待线程的状态。出于这两个原因,需要有一个循环,当线程从等待中唤醒时,它会重新测试条件。

此外,如果“在线程上使用等待方法的代码”是指将 Thread 对象用作锁的代码,那是另一件要避免做的事情,请参阅API documentation for Thread#join

此实现使用以 this.isAlive 为条件的 this.wait 调用循环。当线程终止时,将调用 this.notifyAll 方法。建议应用程序不要在 Thread 实例上使用 wait、notify 或 notifyAll。

【讨论】:

  • 而且因为它除了锁定之外不做任何事情,你不太可能不小心从公共方法中返回它。
【解决方案2】:

在调用 wait 之前,您首先需要在 Object 上进行同步。这是您获得异常的地方。

void test() {
    String s = "AAA";
    synchronized( s ) {
        s.wait();
    }
}

当你调用 notify 时必须做同样的事情,但在这种情况下,这是一个非常非常糟糕的主意,因为如果一个线程进入这个方法,它将永远不会返回。虽然考虑到它是一个字符串字面量,但您可以通过在同一类的另一个方法中使用相同的字面量来摆脱它,但不要指望它。

【讨论】:

  • 哦,这很讨厌,在实习字符串上同步。 :)
  • 它有效。 pastebin.com/RGiM3wMX。永远不要这样做。任何通过的人都会对发生的事情感到非常困惑,而且字符串可能也被跨类实习,这只是要求随机行为。
  • 我养你numbered monitors。 (顺便说一句,对于阅读本文的任何人,千万不要在家里这样做。)
  • 这非常令人不安,我真的希望以前没有人这样做过。查看 Integer 的来源,它仅适用于通过自动装箱或 Integer.valueOf 创建的整数,介于 -127 和 128 之间,因为这些是缓存的整数。 (我第二次 biziclop 的警告,忘记你听说过这些想法)
  • 那时你比我更相信人性。我敢肯定,有一位开发人员遇到了这个问题,并真的认为这是一个聪明的主意。
【解决方案3】:

wait() 方法在Object 中实现,String 扩展了对象,因此可以使用。 为什么有人使用它?问他。这不是一个编程问题。

我能想到的:

他可能在一个类中使用"lock1".wait(),在另一个类中使用"lock1".notify(),这将类似于全局锁定对象 因为文字是由 编译器,因此引用同一个对象 但这是非常非常糟糕的做法

【讨论】:

  • 它可能是,可能有一个很好的,一般的实际原因。但真的没有。
  • 是的,但我的意思是,例如,如果问题是人们为什么写"foo".equals( bar ) 而不是bar.equals( "foo" ),那么会有一个编程答案。在这种情况下没有,但你必须先问这个问题:)
  • 他可能正在使用"lock1".wait()"lock1".notify() 这将类似于全局锁变量
  • 嗨 Dima,我正在为 OCJP 6 做准备,并在其中一次考试中找到了此代码。所以我想知道你是否有答案。如果我认识这个人,我就不会在这里发帖了,对吧?
  • 这就是为什么我添加了一些我能想到的东西,看看它
【解决方案4】:

这是一个没有影响的同步示例。

首先,您不太可能需要在String 上进行同步,毕竟它是不可变的,因此您不需要它来异步执行任何操作。

其次,你很可能在不正确的对象上进行同步,没有正确编写的程序会使用String 作为同步锁。

第三也是最后一点,s 是一个局部变量。事实上,它与 JCIP 明确告诉你不要在你内联它时使用的模式完全相同:

synchronized (new Object()) {
    // ...
}

这是没有效果的同步,因为它不能保证 synchronized 关键字的用途:序列化访问、锁定和释放语义,要求在任何给定时间只有一个线程执行同步块。

因此,每个线程都会有自己的锁——不好。

【讨论】:

  • "你不太可能需要在 String 上同步,毕竟它是不可变的" 我的代码同步的对象是总是不可变的:每当我的一个方法同步时,它看起来像:synchronized(lock) { ... },并且锁变量声明如下:private final Object lock = new Object(); 始终是私有的,始终是最终的,并且始终是不可变 Object 类的直接实例。
  • 回复:“没有正确编写的程序会使用字符串......”“正确”和“合理”或“漂亮”甚至“远程理智”之间存在差异。一个正确程序是一个做它应该做的事情的程序。将使用裸Object 实例作为锁的正确程序更改为使用String 实例作为锁的正确程序将非常容易。
  • 回复:“s 是一个局部变量。”示例中的局部变量 s 包含对 interned 字符串的引用。同一个程序中的其他方法完全有可能(而且完全是邪恶的)只需在源代码中使用相同的字符串字面量就可以在同一个 String 对象上同步。
  • @jameslarge 至于你的第一句话,我认为人们会同步字符串以便安全地调用它的方法。至于你的第二个,我认为你不会那么技术性来区分不同级别的“正确性”。最后,对于您的第三个,这是特定于实现的,不能完全保证,所以我把它省略了。
  • “人们会同步字符串以便安全地调用它的方法”——这没有任何意义。锁定对象和正在同步的操作之间不必有任何关系,而且通常没有任何关系。
猜你喜欢
  • 2011-08-04
  • 1970-01-01
  • 1970-01-01
  • 2021-01-06
  • 2012-01-30
  • 1970-01-01
  • 1970-01-01
  • 2018-07-09
  • 1970-01-01
相关资源
最近更新 更多