什么可以导致同步块内的IllegalMonitorStateException?

时间:2011-09-21 15:59:23

标签: java synchronization illegalmonitorstateexcep

今天我们遇到了一个非常令人惊讶的例外。在同步块内部,我们调用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
  • 找不到任何相关内容

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

  • 未捕获的内存不足错误会导致显示器状态被搞砸吗?我们正在考虑这个问题,但我们还没有发现内存错误的证据。

更新(根据评论)

我还从stacktrace和断点验证,当抛出异常时,线程确实在synchronized块内。事实并非如此,其他一些不相关的代码会发出异常(除非有些东西真的让Eclipse感到困惑!)

4 个答案:

答案 0 :(得分:6)

唯一可疑的是我看到你将'this'的引用传递给构造函数中的其他对象。是否有可能(事实上,并非不可能),通过奇怪的重新排序,如果某个其他线程获得对'this'的引用并调用使用muxlock的方法,那么事情就会出错。

Java Language Specification对此非常具体:

  

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

换句话说,如果另一个线程在构造函数完成之前获得了'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/index.html

答案 1 :(得分: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否则你会等待(0)或等待(-n)这可能导致IllegalMonitorStateException

编辑:Ok以上是一场灾难但是试试吧:

我们在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 第112行我们使用新的处理程序()注册到通道。

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

现在我们仍然在构造函数中,所以最终的对象仍然是可变的! 让我们假设它发生变化,现在我们在不同的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是executet,当我们想要开始进行处理时)

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

但最好不要在多路复用器的构造函数中创建SocketChannelConnectionIO,但可能会在第二个构造函数创建StreamConnectionIO后使用此

答案 2 :(得分:1)

答案在我看来,它是一个错误,或者有人改变了引用背后的对象,尽管它是最终的。如果你可以重现它,我建议在muxlock字段上设置一个读/写断点,看它是否被触摸。您可以在synchronized块的第一行中检查muxlock的identityhashcode,然后等待并通知相应的日志条目或断点。通过反射,您可以更改最终参考。引自http://download.oracle.com/javase/6/docs/api/java/lang/reflect/Field.html

如果基础字段是final,则该方法抛出IllegalAccessException ,除非此字段 setAccessible(true)成功且此字段是非静态的。设置a以这种方式的最终字段仅在反序列化或重建具有空白最终字段的类的实例期间才有意义,然后才能使程序的其他部分访问它们。在任何其他上下文中使用可能具有不可预测的影响,包括其他情况程序的某些部分继续使用该字段的原始值。“

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

答案 3 :(得分:1)

成员变量并不像人们希望的那样最终。您应该首先将synchronized对象放入最终的局部变量中。这并不能解释成员变量被改变的原因,但是如果它解决了问题,你至少知道成员变量真的被修改了。