简体   繁体   English

为什么在同步块外部调用notifyAll()时引发IllegalMonitorStateException?

[英]Why the IllegalMonitorStateException is raised upon notifyAll() call outside a synchronized block?

Currently I'm reading the chapter Guarded Blocks of the online Java Concurrency tutorial. 目前,我正在阅读在线Java 并发教程中的保护块一章。 As an exercise I created a class to see in practice the proper use of wait() and notifyAll() methods. 作为练习,我创建了一个类以在实践中查看对wait()和notifyAll()方法的正确使用。 Yet, there is something in my own code which I'm not able to understand and I would appreciate if you could kindly give me a hand. 但是,我自己的代码中有些东西我听不懂,如果您能帮助我,我将不胜感激。

Environment: 环境:

OS: Fedora Core 17 X86_64
JDK: 1.8.0_05 (64 Bit)

Test case specification: 测试用例规范:

  • Define a class that creates and starts 4 threads, 定义一个创建并启动4个线程的类,
  • The run() method of each thread is in fact an infinite loop and will stop when the user does CTRL+C, 每个线程的run()方法实际上是一个无限循环,当用户执行CTRL + C时将停止运行,
  • Each of these threads has to print one letter among {A, B, C, D}, 每个线程都必须在{A,B,C,D}中打印一个字母,
  • No matter which one among the four created threads is the current running thread, the alphabetic order of the letters must be respected comparing to the last printed letter. 不管创建的四个线程中的哪个是当前运行的线程,与最后打印的字母相比,都必须遵守字母的字母顺序。
  • Start by printing the letter 'A' 首先打印字母“ A”

The output which is expected is therefore something like this on the terminal: 因此,期望的输出在终端上是这样的:

A
B
C
D
A
B
C
D
A
B
C
D
...

test case implementation: 测试用例的实现:

/*
My solution is based on a shared lock among threads.
This object has one attribute: a letter, indicating 
the letter that must be printed on the user terminal.
*/
class SharedLock
{
    private char letter;

    public SharedLock(char letter)
    {
        this.letter = letter;
    }

    /*
        Every thread which is owner of the shared lock's
        monitor call this method to retrieve the letter 
        that must be printed according to the alphabetic order.
    */
    public synchronized char getLetter()
    {
        return this.letter;
    }

    /*
        Every thread which is the owner of the shared lock's 
        monitor and besides has just printed its letter, before 
        releasing the ownership of the shared lock's monitor,
        calls this method in order to set the next 
        letter (according to the alphabetic order) to 
        be printed by the next owner of the shared 
        lock's monitor
    */
    public synchronized void setLetter(char letter)
    {
        this.letter = letter;
    }
}


/*
As said earlier each thread has a letter attribute.
So if I create 4 threads, there will be one thread 
for each letter, one which prints only 'A', another 
which prints only 'B', and so on.

Besides each thread's constructor takes as second 
parameter: the shared lock object (described above).

If the letter attribute of a thread which is the owner 
of the shared lock's monitor, is the same as 
the shared lock's letter attribute, then the thread can
print its letter because it respects the alphabetic order
otherwise it has to wait.
*/
class LetterPrinter implements Runnable
{
    private char letter;
    private SharedLock lock;

    public LetterPrinter(char letter, SharedLock lock)
    {
        this.letter = letter;
        this.lock = lock;
    }

    public void run()
    {
        while(true)
        {
            // Here the current thread tries to become the owner of
            // the shared lock's monitor
            synchronized(this.lock)
            {
                /*
                    Test whether the letter attribute of this 
                    thread must be printed. This will happen
                    only if the letter of the shared lock and
                    the thread's letter attribute are the same.
                */
                while(this.lock.getLetter() != this.letter)
                {
                    try
                    {
                        // The letters are different so in order to respect 
                        // the alphabetic order this thread has to wait
                        this.lock.wait();
                    }
                    catch(InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
            }

            // printing the letter
            System.out.format("%s: %s%n", 
                Thread.currentThread().getName(), this.letter);

            // preparing for the next letter print according to the 
            // alphabetic order
            switch (this.letter)
            {
                case 'A': this.lock.setLetter('B'); break;
                case 'B': this.lock.setLetter('C'); break;
                case 'C': this.lock.setLetter('D'); break;
                case 'D': this.lock.setLetter('A'); break;
            }

            // And finally releasing the ownership of 
            // the shared lock's monitor
            synchronized(this.lock)
            {
                this.lock.notifyAll();
            }
        }
    }
}

public class MyTestClass
{
    public static void main(String[] args) 
    {
        // creating the shared lock object which is initialized
        // by the letter 'A'. This was the problem specification 
        // we wish to start by 'A'
        SharedLock lock = new SharedLock('A');

        // Creates the four threads with their distinct letter and 
        // their shared lock
        Thread thread1 = new Thread(new LetterPrinter('A', lock));
        Thread thread2 = new Thread(new LetterPrinter('B', lock));
        Thread thread3 = new Thread(new LetterPrinter('C', lock));
        Thread thread4 = new Thread(new LetterPrinter('D', lock));

        // And starting all of the four created threads above.
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

This program actually produces the desired output and seems to me to do the job correctly (please correct me if I'm wrong). 该程序实际上会产生所需的输出,对我来说似乎可以正确完成工作(如果我错了,请纠正我)。 Yet if you look at the run() method above you will see that at the end the notify() call has also been placed in a synchronized block. 但是,如果您看一下上面的run()方法,您将发现最后notify()调用也已放置在同步块中。

Just to see what happens, I eliminated the synchronized block and I just wrote the notify() alone for releasing the ownership of the lock's monitor and I got 只是为了看看会发生什么,我消除了同步块,我只写了notify()来释放锁的监视器的所有权,我得到了

Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
        at java.lang.Object.notifyAll(Native Method)
        at LetterPrinter.run(MyTestClass.java:105)
        at java.lang.Thread.run(Thread.java:745)

According to the documentation of IllegalMonitorStateException : 根据IllegalMonitorStateException的文档:

public class IllegalMonitorStateException extends RuntimeException 公共类IllegalMonitorStateException扩展了RuntimeException

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. 抛出该异常指示线程试图在对象的监视器上等待,或者通知其他线程在对象监视器上等待而没有拥有指定的监视器。

Which is exactly my question. 这正是我的问题。 Why? 为什么?

why notify call, when the ownership of the shared lock is being released by the current owner, must also be placed within a synchronized block? 当当前所有者释放共享锁的所有权时,为什么还必须将通知放到一个同步块中?

According to the documentation of notify() and notifyAll() : 根据notify()notifyAll()的文档:

A thread becomes the owner of the object's monitor in one of three ways: 线程通过以下三种方式之一成为对象监视器的所有者:

  • By executing a synchronized instance method of that object. 通过执行该对象的同步实例方法。
  • By executing the body of a synchronized statement that synchronizes on the object. 通过执行在对象上同步的同步语句的主体。
  • For objects of type Class, by executing a synchronized static method of that class. 对于类类型的对象,通过执行该类的同步静态方法。

Only one thread at a time can own an object's monitor. 一次只能有一个线程拥有对象的监视器。

And the second one that is, a synchronized statement on the lock is what I do. 第二个是在锁上执行同步语句,这就是我要做的。 Therefore every thread which is not the good one (according to the alphabetic order) waits. 因此,不是好线程的每个线程(根据字母顺序)都在等待。 So when the notify() on the lock is executed, this can be run only by a thread which is the owner of its monitor and no other thread can try to run this as all others are waiting. 因此,当执行锁上的notify()时,只能由作为其监视器所有者的线程运行,而其他所有线程都在等待,则其他线程无法尝试运行该线程。

So I don't understand why putting the notify() call at the end of the run() method outside a synchronized block, raises the IllegalMonitorStateException exception? 所以我不明白为什么将notify()调用放在run()方法末尾的同步块之外,会引发IllegalMonitorStateException异常?

I'm rather beginner in concurrency. 我是并发的初学者。 Clearly it seems that there is something about the execution of the statements and the OS scheduler which I misunderstand. 显然,我对某些语句和OS调度程序的执行似乎有所误解。

Could someone kindly make some clarification? 有人可以澄清一下吗?

The answer is in one of the javadocs you quoted: 答案在您引用的其中一个javadocs中:

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. 抛出该异常指示线程试图在对象的监视器上等待,或者通知其他线程在对象监视器上等待而没有拥有指定的监视器。

You have to synchronize on a monitor before waiting on it or notifying a/the thread(s) waiting on it, and that waiting/notifying has to be done inside the synchronized block. 您必须先在监视器上进行同步,然后再等待它或通知正在等待的线程,并且必须已同步的块完成等待/通知。 As soon as you exit the synchronized block, you no longer own the monitor. 退出同步块后,您将不再拥有监视器。

As for the reason you need to own the monitor before waiting/notifying, it's to prevent race conditions, since monitors are usually used for communication between threads. 由于您需要在等待/通知之前拥有监视器的原因,这是为了防止出现竞争情况,因为监视器通常用于线程之间的通信。 Ensuring only one thread has access to a monitor at a time ensures that all other threads will see "changes" to it. 确保一次只有一个线程可以访问监视器,以确保所有其他线程都可以看到它的“更改”。

Also, minor quibble: In your test case, you release the lock before printing, and regain it after printing. 此外,还有一些小问题:在您的测试用例中,您在打印之前释放了锁,并在打印后重新获得了锁。

This seems to have worked in your case, perhaps because it seems only one thread is woken up at a time, but if another thread wakes up by itself (called a spurious wakeup ), you could very well get letters out of order. 在您的情况下,这似乎奏效,也许是因为似乎一次只唤醒一个线程,但是如果另一个线程本身被唤醒 (称为伪唤醒 ),则很可能会使字母混乱。 I don't expect it to be a common thing though. 我不认为这是平常的事。

Another way this could go wrong is if it just happens that one thread starts, gets past the lock, gets halted before printing, another thread comes in, prints, etc. 这可能会出错的另一种方式是,如果恰好发生一个线程启动,越过锁,在打印之前停止,另一个线程进入,打印等情况。

What you want to do is keep the lock throughout the entire method, so you're guaranteed to have only one thread printing at a time. 您要做的是在整个方法中保持锁定状态,因此可以确保每次仅打印一个线程。

Not a complete answer, just adding to what user3580294 already said: 没有完整的答案,只是添加到user3580294已经说过的内容:

Synchronization does more than just prevent two threads from entering the same critical section at the same time. 同步不仅可以防止两个线程同时进入同一关键部分,还可以做更多的事情。 It also guarantees synchronization of the per cpu memory caches on a multiprocessor machine. 它还保证了多处理器计算机上每个cpu内存缓存的同步。 The java language spec makes this guarantee; Java语言规范对此做出了保证; If thread A updates a field (ie, an instance variable or a class variable) and then releases a lock, and then thread B acquires the same lock, then thread B will be guaranteed to see the new value that thread A wrote to the field. 如果线程A更新了一个字段(即实例变量或类变量),然后释放了一个锁,然后线程B获得了相同的锁,那么将保证线程B看到线程A写入该字段的新值。 。 Without synchronization, there is no guarantee if or when one thread will see new values in fields that were updated by other threads. 如果没有同步,则无法保证一个线程是否或何时在其他线程更新的字段中看到新值。

Presumeably, if thread A is going to notify an object, it's because thread A changed something that thread B is waiting for. 据推测,如果线程A要通知对象,那是因为线程A更改了线程B等待的内容。 But if thread A did not unlock a lock and thread B did not lock the same lock, then when thread B wakes up, it will not necessarily see what thread A changed. 但是,如果线程A没有解锁锁,线程B没有锁相同的锁,那么当线程B唤醒时,它不一定会看到线​​程A更改了什么。

The language and libraries are designed to not let you make that mistake. 语言和库旨在防止您犯该错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么notifyAll()在Integer上同步时会引发IllegalMonitorStateException? - Why does notifyAll() raise IllegalMonitorStateException when synchronized on Integer? 同步块中的IllegalMonitorStateException - IllegalMonitorStateException inside synchronized block NotifyAll,IllegalMonitorStateException - NotifyAll, IllegalMonitorStateException 如何在不获取java.lang.IllegalMonitorStateException的情况下更改在同步块中获取的锁,对其进行更改以及notifyAll()? - how can I change the lock I have aquired in a synchronized block, change it, and notifyAll() without getting java.lang.IllegalMonitorStateException? 在同步块结束时是否需要notifyAll()? - Is notifyAll() required at the end of a synchronized block? 当我以静态方式同步块调用wait()时,为什么Java会抛出java.lang.IllegalMonitorStateException? - Why Java throw java.lang.IllegalMonitorStateException when I invoke wait() in static way synchronized block? Java等待和notifyAll:IllegalMonitorStateException - Java wait and notifyAll: IllegalMonitorStateException 为什么不调用wait(),notify()或notifyAll()而没有同步块而不是编译器错误? - Why isn't calling wait(), notify() or notifyAll() without a synchronized block not a compiler error? 同步调用Lock Condition的signalAll()时发生IllegalMonitorStateException - IllegalMonitorStateException on a synchronized call to a Lock Condition's signalAll() java notifyAll:非法MonitorStateException - java notifyAll: illegalMonitorStateException
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM