在Java线程通信中,等待通知机制是最传统的方式,就是在一个线程进行了规定操作后,该线程就进入等待状态(wait), 等待其它线程执行完它们的指定代码过后,再将之前等待的线程唤醒(notify)。等待通知机制中使用到wait()、notify()和notifyAll()这三个方法,它们都属于Object这个类中,由于所有的类都从Object继承而来,因此,所有的类都拥有这些共有方法可供使用。而且,由于他们都被声明为final,因此在子类中不能覆写任何一个方法。
1.wait()
该方法只能被锁对象调用,调用能使当前线程跟锁绑定,能够进行后续的唤醒操作。而锁必定从同步方法或同步代码块中获得,因此方法只能写在同步方法或同步代码块内部。使用该方法当前线程进入阻塞状态,同时线程释放锁。直到被唤醒后重新进入就绪状态。
2.notify()
该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象锁如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知,而未被选中的对象仍然保持阻塞状态。
注意:当在同步中调用wait()方法时,执行该代码的线程会立即放弃它在对象上的锁。然而在调用notify()时,并不意味着这时线程会放弃该对象锁,而是要等到程序运行完synchronized代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁。
3.notifyAll()
该方法与notify()方法的工作方式相同,重要的一点差异是:
notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其它的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。
下面举一个生产者消费者的例子:(生产者) 厨师是一个线程,能够生产拉面,(消费者)顾客是一个线程,能够吃拉面。
我们可以进行简单分析:
厨师(生产者):
判断桌上是否有拉面:1.有(等待)2.没(生产拉面,叫醒消费者)
顾客(消费者)
1.有(开吃)2.没(等待)
public class test { public static void main(String[] args) { Foodie f=new Foodie(); Cook c=new Cook(); f.start(); c.start(); } } //桌子 class Desk { // 查看当前该哪个线程执行 public static int foodFlag=0;//默认没有 public static int num=0; //锁对象 public static Object lock=new Object(); } //顾客 class Foodie extends Thread{ @Override public void run() { while(true){synchronized (Desk.lock){ if(Desk.foodFlag==0){ try { Desk.lock.wait();//让当前线程跟锁进行绑定 } catch (InterruptedException e) { throw new RuntimeException(e); } } else{ Desk.num++; System.out.println("吃了第"+Desk.num+"碗面条"); Desk.foodFlag=0; Desk.lock.notifyAll(); } }} } } //厨师 class Cook extends Thread{ @Override public void run() { while(true){ synchronized (Desk.lock){ if(Desk.foodFlag==1){ try { Desk.lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } }else{ Desk.foodFlag=1; System.out.println("做好了一碗面条"); Desk.lock.notifyAll(); } } } } }