java.lang.IllegalMonitorStateException: 线程在wait()之前未锁定对象

51

我正在使用ProgressDialog。当用户关闭ProgressDialog时,我需要停止线程。不幸的是,出现了异常。

在内部类中:

class UpdateThread extends Thread{

    public  void run() {
        while (true){
            count=adapter.getCount();

            try {
               mHandler.post(  new Runnable() {
                    public  void run() {
                        Log.i(TAG,count+"count");
                        progressDialog.setMessage(count + "Device  found");
                    }
                });
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

OnCreate:

 updateThread=new UpdateThread();

 progressDialog= new ProgressDialog(GroupListActivity.this);
 synchronized (this) {
     updateThread.start();
 }

在解雇时:

   progressDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
        @Override
        public  void onDismiss(DialogInterface dialog) {
            try {
                synchronized (this) {
                    updateThread.wait(300);
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.i(TAG,"Thread is stopped");
        }
    });

你在使用锁时究竟想要达成什么目的? - Krease
@Chris,它正在抛出异常。所以我必须锁定。 - Asthme
1
我理解如果你要调用wait,就必须使用synchronized,但我不明白(a)为什么在调用updateThread.start时也要同步,或者(b)为什么要使用wait(因为你没有使用notifynotifyAll)。我怀疑你在不应该的地方进行了同步和使用wait - Krease
添加了一个答案,更改了您的代码以实现您似乎想要做的事情。 - Krease
3个回答

68

这是错误的:

synchronized(foo) {
    foo.wait();
}

问题在于,如何唤醒这个线程呢?也就是说,你怎么能够保证在第一个线程调用foo.wait()之前,另一个线程不会调用foo.notify()?这很重要,因为如果先调用了notify(),那么foo对象将不会记得它被通知过。如果只有一个notify(),并且它发生在wait()之前,那么wait()将永远不会返回。

以下是wait和notify的正确使用方式:

private Queue<Product> q = ...;
private Object lock = new Object();

void produceSomething(...) {
    Product p = reallyProduceSomething();
    synchronized(lock) {
        q.add(p);
        lock.notify();
    }
}

void consumeSomething(...) {
    Product p = null;
    synchronized(lock) {
        while (q.peek() == null) {
            lock.wait();
        }
        p = q.remove();
    }
    reallyConsume(p);
}
在这个例子中要注意的最重要的事情是有一个明确的条件测试(即,q.peek() != null),并且没有人在不锁定锁的情况下更改条件。如果先调用了消费者,它将发现队列为空,然后等待。生产者可以插入、向队列添加产品并通知锁的时机直到消费者准备好接收通知。另一方面,如果先调用生产者,则保证消费者不会调用wait()。消费者中的循环有两个重要原因:其一是,如果有多个消费者线程,则一个消费者可能会接收通知,但另一个消费者会悄悄地插入并从队列中窃取该产品。在这种情况下,唯一合理的做法是让第一个消费者再次等待下一个产品。另一个原因是Javadoc说即使对象没有被通知,也允许Object.wait()返回。这称为"虚假唤醒",处理它的正确方法是返回并再次等待。还要注意:锁是私有的,队列也是私有的。这确保了没有其他编译单元会干扰这个编译单元中的同步。另请注意:锁是与队列本身不同的对象。这确保了该编译单元中的同步不会干扰队列实现所做的任何同步(如果有)。请注意:我的例子重新发明了一个轮子来证明一个观点。在实际代码中,你将使用ArrayBlockingQueue的put()和take()方法,它们将为你处理所有的等待和通知。

很棒的解释。我会尝试集成并告诉您。 - Asthme
wait() 会在阻塞其线程之前释放对象监视器,因此不会阻塞调用 notify 的其他线程。 - Mygod
@Mygod,是的。调用o.wait()会释放调用线程对o的锁定,然后等待通知,最后在返回之前重新获取锁定。 - Solomon Slow
@jameslarge 我不知道这个,所以我以为这段代码不能工作。 - Mygod
我会在私有对象锁上添加“final”修饰符。 - activity

11

只有当您已经持有对象的锁时,才能在它上面等待,您可以尝试:

synchronized (updateThread) {
    updateThread.wait(300);
}

...但我不是很确定你想通过这些锁达到什么目的。


0

看起来你正在尝试在不应该使用的地方使用 synchronizedwait

如果你真的想等待线程完成,你应该像这样做:

在你的 UpdateThread 中:

class UpdateThread extends Thread{
    public AtomicBoolean stopped = new AtomicBoolean(false);
    public  void run() {
        while (!stopped.get()){
    .....

在你的创作中:
updateThread = new UpdateThread();
progressDialog = new ProgressDialog(GroupListActivity.this);
updateThread.start();    // no synchronization necessary

在你的dismiss方法中:

progressDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
        @Override
        public  void onDismiss(DialogInterface dialog) {
            try {
                updateThread.stopped.set(true);
                updateThread.join(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.i(TAG,"Thread is stopped");
        }
    });

注意,我为您的线程添加了一个退出条件,这样它才会停止(现在,您的线程将继续运行)。您可能希望将退出条件设为私有,并添加一个setter以使代码更加简洁。此外,我使用 join 来等待您的线程完成。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接