关于Java wait()方法

        每一个对象除了有一个之外,还有一个等待队列(wait set),当一个对象刚创建的时候,它的等待队列是空的。

我们应该在某个线程获取对象的锁后,在该对象锁控制的同步块或同步方法中去调用该对象的wait方法,将该线程挂起放入该对象等待队列。

当调用该对象的notify方法时,将从该对象的等待队列中随机唤醒一个线程,这个线程将再次成为可运行的线程。

所以我们使用wait和notify方法的时候必须确定这两个线程在同一个对象(必须是控制该同步块或同步方法的对象)的等待队列。


=====================================

  wait() 是否会导致死锁?


wait()和notifyAll()提供了线程协作的方式(另外一种是线程互斥的方式),会不会出现死锁的现象?[解释一下协作:A线程需要达到某种状

态S才能继续工作,但是暂时S状态不能由A线程本身满足,所以A线程必须等待S状态的满足(wait);另外的线程B可以达到S状态,当S状态达到

的时候,B可以通知A线程继续(notify),这就是所谓的协作方式。最典型的应用可以是一个队列,有数个线程向队列中增加数据,有数个线程

处理队列中的数据。appender线程和handler线程需要协作]

Java本身的线程机制比较完善,单独从wait机制和notify机制来说,是不会造成死锁的。当线程调用wait,它会释放monitor,并进入等待区。

当等待区中的线程被notify唤醒,它会竞争monitor的锁。得到monitor的线程(持有者)只会有一个。
所以说,单纯的wait和notify机制已经在jvm实现中避免了死锁的情况。

但是,话说来,出现死锁的地方实际上还是由于代码的缺陷所造成的。当然有可能造成死锁。俺们主要可以讨论一个wait的两个关键问题:
(1) DeadLock问题
(2) wait条件问题

先来看DeadLock问题,借用EffectiveJava中的一个例子,下例就可以造成死锁。
<<
public abstract class WorkQueue {
    private final List queue = new LinkedList();
    private boolean stopped = false;

    protected WorkQueue() {
        new WorkerThread().start();
    }

    public final void enqueue(Object workItem) {
        synchronized (queue) {
            queue.add(workItem);
            queue.notify();
        }
    }

    public final void stop() {
        synchronized (queue) {
            stopped = true;
            queue.notify();
        }
    }

    protected abstract void processItem(Object workItem)
            throws InterruptedException;
// Broken - invokes alien method from synchronized block!
    private class WorkerThread extends Thread {
        public void run() {
            while (true) { // Main loop
                synchronized (queue) {
                    try {
                        while (queue.isEmpty() && !stopped)
                            queue.wait();
                    } catch (InterruptedException e) {
                        return;
                    }
                    if (stopped)
                        return;
                    Object workItem = queue.remove(0);
                    try {
                        processItem(workItem); // Lock held!
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }
        }
    }
}
>>
这是一个用LinkedList来实现的队列的例子。enqueue方法向队列中增加元素并通知处理线程,而处理线程代码如WorkerThread所示,从队列中

取出元素并处理,如果队列为空,则等待直到enqueue或者stop方法唤醒它。<<queue.wait();>>。
这个思路好像没有问题,问题在于它把处理流程(synchronized中)做成了抽象:protected abstract void processItem(Object workItem),

这样就把一些问题留给了子类,就有可能造成死锁,比如这样的子类:
<<
class DeadlockQueue extends WorkQueue {
    protected void processItem(final Object workItem)
            throws InterruptedException {
// Create a new thread that returns workItem to queue
        Thread child = new Thread() {
            public void run() {
                enqueue(workItem);
            }
        };
        child.start();
        child.join(); // Deadlock!
    }
}
>>
呵呵,所以说:“死锁还是由开发者的缺陷代码所导致的”。
这个故事告诉俺们:“不要把synchronized块代码的控制交给客户端”。:)


第二个问题wait条件问题,可以这么看:在等待区中的线程丛被唤醒到持有monitor锁,这中间是有时延的。并不能保证在这期间,有其他的线

程持有锁。刚才说了,需要S状态A线程才能继续,现在B线程使S状态被满足并通知了等待中的A。但是在A获取锁之前,有一个C线程抢先获取了

锁,而C线程破坏了S状态。等待A线程获取锁的时候,S状态已经不满足了,但是A线程并不知道这点,可能还是会继续处理。问题当然就可以产

生了。
解决这个问题的办法就是,让等待的线程获取锁的时候一定要检查数据状态,如果不满足,呵呵,继续等待。
所以这个问题告诉俺们:“不要在循环外面调用wait方法”
为什么要在循环内呢?因为要让循环的条件作为状态的检查条件。

展开阅读全文

没有更多推荐了,返回首页