30

我们今天遇到了一个非常令人惊讶的异常。在同步块内部,我们调用 wait() 并抛出IllegalMonitorStateException. 什么会导致这种情况?

这发生在经过充分测试的开源代码中:http: //svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java ?view=markup #l222

我们消除了明显的原因:

  • 我们是否在正确的变量上同步?是的,它是muxLock
  • 它是一个可变变量吗?不,muxLock是最终的
  • 我们是否使用了任何可能影响监视器行为的奇怪“-XX:”JVM 标志?不,但我们正在通过 JNI 启动嵌入在 C++ 应用程序中的 JVM。
  • 这是一个奇怪的JVM吗?不,这是 Sun 的 1.6.0_25 win/x64 JRE
  • 这是一个已知的 JVM 错误吗?在http://bugs.sun.com/bugdatabase找不到任何相关内容

所以,我试图想出更多牵强的解释。

  • 未捕获的内存不足错误会导致监视器状态被搞砸吗?我们正在研究这个,但我们还没有看到内存错误的证据。

更新:(基于评论)

我还从堆栈跟踪和断点验证了当抛出异常时线程确实在同步块内。其他一些不相关的代码不会发出异常(除非某些东西真的让 Eclipse 感到困惑!)

4

4 回答 4

6

我看到的唯一可疑之处是您将对“this”的引用传递给构造函数中的其他对象。是否有可能(事实上,并非不可能)通过对事物进行奇怪的重新排序,如果其他线程获得对“this”的引用并调用使用 muxlock 的方法,事情可能会变得非常错误。

Java 语言规范对此非常具体:

当一个对象的构造函数完成时,它被认为是完全初始化的。只有在对象完全初始化后才能看到对该对象的引用的线程可以保证看到该对象的最终字段的正确初始化值。

换句话说,如果另一个线程在构造函数完成之前获得了“this”引用,那么最终字段“muxlock”可能还没有正确初始化。通常,在构造函数完成之前发布对“this”的引用可能非常危险,尤其是在线程情况下。

关于这些事情的一些潜在有用的讨论:http: //madpropellerhead.com/random/20100328-java-final-fields-are-not-as-final-as-you-may-think

对于一些较旧但仍然有用的一般性讨论,为什么在构造函数中发布“this”通常是一个非常糟糕的主意,例如: http ://www.ibm.com/developerworks/java/library/j-jtp0618/索引.html

于 2011-09-28T12:37:03.007 回答
2

http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java?r1=1069292&r2=1135026&diff_format=h

在这里我可以看到最近添加了超时

确保 startTimeout 大于 0 否则您将 wait(0) 或 wait(-n) 这可能会导致 IllegalMonitorStateException

编辑:好的,上面是一场灾难,但让我们试试这个:

我们在 Mux 构造函数中:http: //svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java ?view=markup

第 176 行我们创建了 SocketChannelConnectionIO 并传递了它,然后我们中断并由不同的线程接管。

在此处定义的 SocketChannelConnectionIO 的构造函数中:http: //svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/SocketChannelConnectionIO.java ?view=markup line 112 我们注册使用新的处理程序()进行通道。

处理程序在 chanel 和函数上接收到一些东西,让我们说函数 handleReadReady 被执行,我们在 muxLock 上同步。

现在我们仍在构造函数中,所以 final 中的对象仍然是可变的!!!假设它发生了变化,现在我们有一些东西在等待不同的 muxLock

百万分之一的场景

编辑

http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java?revision=1135026&view=co

Mux(SocketChannel channel,
    int role, int initialInboundRation, int maxFragmentSize)
    throws IOException
    {
    this.role = role;
    if ((initialInboundRation & ~0x00FFFF00) != 0) {
        throw new IllegalArgumentException(
        "illegal initial inbound ration: " +
        toHexString(initialInboundRation));
    }
    this.initialInboundRation = initialInboundRation;
    this.maxFragmentSize = maxFragmentSize;

    //LINE BELOW IS CAUSING PROBLEM it passes this to SocketChannelConnectionIO
    this.connectionIO = new SocketChannelConnectionIO(this, channel);

    //Lets assume it stops here we are still in constructor
    //and we are not in synchronized block

    directBuffersUseful = true;
    }

现在在 SocketChannelConnectionIO http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/SocketChannelConnectionIO.java?revision=1069292&view=co的构造函数中

SocketChannelConnectionIO(Mux mux, SocketChannel channel)
    throws IOException
{
    super(mux);
    channel.configureBlocking(false);
    this.channel = channel;
    //Line below we are registering to the channel with mux that is still mutable
    //this is the line that actually is causing the problem move that to 
    // start() and it should work 
    key = selectionManager.register(channel, new Handler());
}

将此代码移动到 start() 应该可以工作key = selectionManager.register(channel, new Handler());(我假设 start 在我们想要开始处理时执行)

/**
 * Starts processing connection data.
 */
void start() throws IOException {
    key = selectionManager.register(channel, new Handler());
    key.renewInterestMask(SelectionKey.OP_READ);
}

但是最好不要在 mux 的构造函数中创建 SocketChannelConnectionIO 但也许在那之后的某个地方与第二个构造函数创建 StreamConnectionIO 相同

于 2011-09-29T17:25:48.137 回答
1

在我看来,答案可能是一个错误,或者有人更改了引用背后的对象,尽管它是最终的。如果你能重现它,我建议在 muxlock 字段上设置一个读/写断点,看看它是否被触及。您可以在同步块的第一行以及等待和通知之前使用适当的日志条目或断点检查 muxlock 的身份哈希码。通过反射,您可以更改最终参考。引用http://download.oracle.com/javase/6/docs/api/java/lang/reflect/Field.html

"如果基础字段是最终字段,则该方法将抛出 IllegalAccessException ,除非该字段的 setAccessible(true) 已成功并且该字段是非静态的。以这种方式设置最终字段仅在反序列化或重建类实例期间有意义空白的最终字段,在它们可供程序的其他部分访问之前。在任何其他上下文中使用可能会产生不可预知的影响,包括程序的其他部分继续使用该字段的原始值的情况。

也许它是 eclispe 中的一个错误,并且在调试过程中它以某种方式改变了该领域。它也可以在 eclispe 之外重现吗?将 printstractrace 放入 catch 中,看看会发生什么。

于 2011-09-23T21:11:44.310 回答
1

成员变量并不像人们希望的那样最终。您应该首先将同步对象放入最终的局部变量中。这并不能解释为什么要更改成员变量,但是如果它解决了问题,您至少知道成员变量确实被修改了。

于 2011-09-29T16:25:33.020 回答