Java中的wait(),notify()和notifyAll()方法
Java中的wait(),notify()和notifyAll()方法用于线程间通信。 Java中的每个对象都有一个关联的锁,并且持有该锁(由当前线程)的对象用于线程之间的通信。关于Java中的wait(),notify()和notifyAll()方法,有两点很重要:
1这些方法在Object类中作为最终方法实现。由于Object类是Java中所有类的超类,因此wait(),notify()和notifyAll()方法在所有类中均可用。
Object类中的wait(),notify()和notifyAll()方法声明如下:
- public final void wait() throws InterruptedException
- public final void notify()
- public final void notifyAll()
请参阅为什么在对象类中使用wait(),notify()和notifyAll()方法,以了解将这些方法放入对象类的原因。
2wait(),notify()和notifyAll()方法必须在同步方法或者块中调用。如果我们在未同步的方法中调用这些方法,则程序会编译,但在运行时会抛出IllegalMonitorStateException。
例如,在下面的代码中,在同步块的外部调用了wait()方法,代码将进行编译,但是在运行时将抛出IllegalMonitorStateException。
public void increment(){ synchronized(this){ for(int i = 1; i <= 5 ; i++){ System.out.println(Thread.currentThread().getName() + " i - " + i); } } try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
输出:
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Unknown Source) at com.theitroad.Counter.increment(SynchronizedDemo.java:10) at com.theitroad.SynchronizedDemo.run(SynchronizedDemo.java:31)
我们可以在已使用锁进入同步上下文的对象上调用wait(),notify()和notifyAll()方法。如果使用任何其他对象,则代码也将编译,但是IllegalMonitorStateException将在运行时引发。
Java中的wait()方法
wait()方法使当前线程进入等待状态。这里的当前线程是指当前在同步上下文中与之一起执行并拥有该对象的监视器锁定的线程。
Object类中的wait()方法已重载,并且具有三个变体。
- final void wait()–使当前线程等待,直到另一个线程调用notify或者notifyAll方法或者该线程被中断为止。
- final void wait(长时间超时)–使当前线程等待,直到另一个线程调用notify或者notifyAll方法,该线程被中断或者最大等待时间(以毫秒为单位)到期。
- 最终无效等待(长超时,int nanos)–使当前线程等待,直到另一个线程调用notify或者notifyAll方法,该线程被中断或者最长等待时间(以毫秒为单位)加上以纳秒为单位的额外时间到期为止。
Java中的notify()方法
唤醒正在该对象的监视器上等待的单个线程。如果有一个以上的线程正在等待该对象,则可以任意选择将其中一个唤醒。
在当前线程放弃对该对象的锁定之前,唤醒的线程将无法继续。如果任何其他线程试图获取对该对象的锁定以进入同步方法或者块,则唤醒的线程也将以常规方式与它们竞争,而没有任何特殊的优点或者缺点。
Java中的notifyAll()方法
唤醒正在此对象监视器上等待的所有线程,而不是单个线程。在当前线程放弃对该对象的锁定之前,唤醒的线程将无法继续。同样,这些唤醒的线程必须与其他任何试图获取该对象锁的线程竞争。
虚假唤醒
在极少数情况下,等待中的线程可以在不被通知,中断或者超时的情况下唤醒,这称为虚假唤醒。应用程序必须通过在检查线程正在等待的条件的循环中放置对wait()的调用来防范此情况。
synchronized (obj) { while (<condition does not hold> and <timeout not exceeded>) { long timeout = ... ; // recompute timeout values int nanos = ... ; obj.wait(timeout, nanos); } ... // Perform action appropriate to condition or timeout }
参考https://docs.oracle.com/javase/10/docs/api/java/lang/Object.html#wait(long,int)
Java等待,通知,notifyAll示例
在实践中展示wait(),notify()和notifyAll()方法的一个很好的例子是使用两个线程实现生产者使用者。生产者线程在这里产生一条消息并将其放入列表中,在此期间,消费者线程应等待。一旦列表中有消息,就应通知使用者线程。当使用者线程正在使用消息时,生产者线程应处于等待状态;在使用当前消息时,应被通知将另一条消息放入列表中。列表对象是共享对象,在此应调用等待和通知方法。
import java.util.ArrayList; import java.util.List; // This class produces message and puts it in a shared list class ProduceMsg implements Runnable{ List<String> msgObj; ProduceMsg(List<String> msgObj){ this.msgObj = msgObj; } @Override public void run() { // running it 5 times for(int i = 1; i <= 5; i++){ synchronized (msgObj) { // loop checking wait condition to avoid spurious wakeup while(msgObj.size() >= 1){ try { msgObj.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } msgObj.add("Hello-" + i); System.out.println("Adding to list - " + msgObj.get(0)); msgObj.notify(); } } } } // This class consumes message from a shared list class ConsumeMsg implements Runnable{ List<String> msgObj; ConsumeMsg(List<String> msgObj){ this.msgObj = msgObj; } @Override public void run() { for(int i = 1; i <= 5; i++){ synchronized (msgObj) { // loop checking wait condition to avoid spurious wakeup while(msgObj.size() < 1){ try { msgObj.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // Getting value from the list System.out.println("Getting from queue - " + msgObj.get(0)); msgObj.remove(0); msgObj.notify(); } } } } public class InterTDemo { public static void main(String[] args) { List<String> msgObj = new ArrayList<String>(); // Creating Producer thread Thread t1 = new Thread(new ProduceMsg(msgObj)); // Creating Consumer thread Thread t2 = new Thread(new ConsumeMsg(msgObj)); t1.start(); t2.start(); } }
输出:
Adding to list - Hello-1 Getting from queue - Hello-1 Adding to list - Hello-2 Getting from queue - Hello-2 Adding to list - Hello-3 Getting from queue - Hello-3 Adding to list - Hello-4 Getting from queue - Hello-4 Adding to list - Hello-5 Getting from queue – Hello-5
如果我们在此处不使用wait()和notify()方法进行线程间通信,而只是在共享库上进行同步,则仍然只有一个线程会获取该锁,但是没有什么可以阻止线程在进入线程后执行一次以上同步块。我们可以通过注释等待和通知方法的代码来运行同一示例。
// This class produces message and puts it in a shared list class ProduceMsg implements Runnable{ List<String> msgObj; ProduceMsg(List<String> msgObj){ this.msgObj = msgObj; } @Override public void run() { // running it 5 times for(int i = 1; i <= 5; i++){ synchronized (msgObj) { // loop checking wait condition to avoid spurious wakeup /* while(msgObj.size() >= 1){ try { msgObj.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }*/ msgObj.add("Hello-" + i); System.out.println("Adding to list - " + msgObj.get(0)); msgObj.notify(); } } } } // This class consumes message from a shared list class ConsumeMsg implements Runnable{ List<String> msgObj; ConsumeMsg(List<String> msgObj){ this.msgObj = msgObj; } @Override public void run() { for(int i = 1; i <= 5; i++){ synchronized (msgObj) { /* // loop checking wait condition to avoid spurious wakeup while(msgObj.size() < 1){ try { msgObj.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }*/ // Getting value from the list System.out.println("Getting from queue - " + msgObj.get(0)); msgObj.remove(0); msgObj.notify(); } } } }
输出:
Adding to list - Hello-1 Getting from queue - Hello-1 Exception in thread "Thread-1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(Unknown Source) at java.util.ArrayList.get(Unknown Source) at com.theitroad.ConsumeMsg.run(InterTDemo.java:54) at java.lang.Thread.run(Unknown Source) Adding to list - Hello-2 Adding to list - Hello-2 Adding to list - Hello-2 Adding to list – Hello-2
如我们在此处看到的,消费者线程在消耗一条消息后尝试从列表中获取另一条消息,因为一旦它进入了同步块,没有什么可以阻止它继续执行。由于列表已经为空,因此将导致IndexOutOfBoundsException。