天天看點

詭異的java.lang.IllegalMonitorStateException

今天的一段代碼抛出了java.lang.IllegalMonitorStateException,代碼如下:

private boolean wait = false; public boolean pleaseWait() { synchronized (this.wait) { if (this.wait == true) { return false; } this.wait =true; try { this.wait.wait(); } catch (InterruptedException e) { } return true; }

JavaDoc上關于IllegalMonitorStateException的解釋是:

Thrown to indicate that a thread has attempted to wait on an object's monitor or to notify other threads waiting on an object's monitor without owning the specified monitor.

看起來有些晦澀難懂,比如object's monitor。其實,在Object.notify()這個函數的JavaDoc中有相關的解釋:

A thread becomes the owner of the object's monitor in one of three ways:

1. By executing a synchronized instance method of that object.

2. By executing the body of a synchronized statement that synchronizes on the object.

3. For objects of type Class, by executing a synchronized static method of that class.

說白了,就是需要在調用wait()或者notify()之前,必須使用synchronized語義綁定住被wait/notify的對象。

可問題是,在上面的代碼中,已經對this.wait這個變量使用了synchronzied,然後才調用的this.wait.wait()。按理不應該抛出這個異常。

上網查了很久,終于找到了答案:

真正的問題在于this.wait這個變量是一個Boolean,并且,在調用this.wait.wait()之前,this.wait執行了一次指派操作:

this.wait = true;Boolean型變量在執行指派語句的時候,其實是建立了一個新的對象。簡單的說, 在指派語句的之前和之後,this.wait并不是同一個對象。

synchronzied(this.wait)綁定的是舊的Boolean對象,而this.wait.wait()使用的是新的Boolean對象。由于新的Boolean對象并沒有使用synchronzied進行同步,是以系統抛出了IllegalMonitorStateException異常。

相同的悲劇還有可能出現在this.wait是Integer或者String類型的時候。

一個解決方案是采用java.util.concurrent.atomic中對應的類型,比如這裡就應該是AtomicBoolean。采用AtomicBoolean類型,可以保證對它的修改不會産生新的對象。

正确的代碼:

private AtomicBoolean wait = new AtomicBoolean(false); public boolean pleaseWait() { synchronized (this.wait) { if (this.wait.get() == true) { return false; } this.wait.set(true); try { this.wait.wait(); } catch (InterruptedException e) { } return true; } }