Java多线程是个很复杂的问题,尤其在多线程在任何给定的时间访问共享资源需要更加注意。Java 5引入了一些类比如BlockingQueue 和Executors 类提供了易于使用的API,避免了一些复杂性。使用这些类比直接使用wait()和notify()同步处理的让程序员感到更加自信。我也推荐使用这些新的API来同步,但是很多时候我们出于各种原因需要旧方式,例如维护遗留代码。在这些情况下,熟悉这些方法将有助于你理解。在本教程中,我讨论关于wait(),notify()和notifyall()的一些概念。
什么是wait(),notify()和notifyall()方法?
在进入概念之前,让我们记下这些方法的基本定义。
在Java中Object类有三个final方法允许线程了解资源的锁定状态,它们是:
1. wait():
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。
它告诉调用线程放弃锁。我们不可能用纯Java实现wait()法:它是一个native method。
它被用来使线程等待某个条件,它必须在同步区域内部被调用,这个同步区域将对象锁定在了调用wait方法的对象上。始终应该使用wait循环模式来调用wait方法,永远不要在循环之外调用wait方法。循环会在等待之前和之后测试条件。
在等待之前测试条件,当条件已经成立时立即就跳过等待,这对确保活性时必要的。
在等待之后测试条件,如果条件不成立的话继续等待,这对于确保安全性时必要的。
一般使用wait()方法的语法是如下:
synchronized( lockObject )
{
while( ! condition )
{
lockObject.wait();
}
//take the action here;
}
2.notify():
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
唤醒在同一对象上调用wait()处于等待的线程。应该指出的是,调用notify()实际上并没有放弃对资源上的锁。它告诉等待线程可以唤醒。然而,如果对某一资源调用notify(),但同步块内的资源的执行还需要进行10秒,那么线程需要等待额外的10秒,对象上的锁被释放,被唤醒的线程才能执行。
一般调用notify()方法的语法如下:
synchronized(lockObject)
{
//establish_the_condition;
lockObject.notify();
//any additional code if needed
}
3.notifyall():
它唤醒所有在同一对象上调用wait()的线程。在大多数情况下,优先级最高的线程将首先运行,但没有保证。其他的都和notify()方法一样。
一般调用notifyall()方法的语法如下:
synchronized(lockObject)
{
establish_the_condition;
lockObject.notifyAll();
}
在一般情况下,总应该调用notifyAll将唤醒所有需要被唤醒的线程。你可能也会唤醒其他一些线程,但这不影响程序的正确性,这些线程醒来之后,会检查他们正在等待的条件,如果发现条件不满足,就会继续等待
到目前为止,我们学到了一些你可能已经知道的基本知识。让我们编写一个小程序来理解如何使用这些方法获得预期的结果。
如何使用wait(),notify()和notifyall()方法
在这个练习中,我们将使用wait()和notify()方法解决生产者消费者问题。让程序简单,关注于wait()和notify()方法的使用,我们将只涉及一个生产者和消费者线程。
该程序的其他特点是:
1)生产者线程每秒生产新资源,并放在taskQueue中。
2)消费者线程需要1秒的过程从taskQueue中消耗资源。
3)对taskqueue最大容量是5,即在任何给定的时间最大可以存在5个资源在taskQueue中,。
4)两个线程无限运行。
设计生产者线程
下面是基于我们需求的生产者线程代码:
class Producer implements Runnable
{
private final List