先抛出一个例子:
现在我们有一个馒头的例子。有做馒头的师傅,有个篮子,也有吃馒头的人。然后我们要用程序来模拟这个东西。
这是篮子的图示,其中因为篮子有个特点就是后放进去的先拿出来,陷进去的后拿出来,所以决定用数据结构栈来模拟它,这是一种据说也是先进后出的数据结构喔。
那么篮子满了6个怎么办呢?这里引入一个wait方法,注意它不是我们Thread类里的方法,而是我们的老祖宗Object的方法。 注意它的意思不是让当前对象wait 它的描述是:当前的正在我这个对象访问的线程wait,注意!!只有有锁也就是有synchronized的方法的对象才能wait!!! 还有一个是,wait之后那个锁就不再归我所有,而sleep不一样,睡着了也抱着那把锁。this.wait()
然后与wait相对应的方法是notify,它的意思是:叫醒一个现在正在wait在我这个对象上的线程。this.notify()
也就是说,这个锁住的对象遇到了某个事件,像上面例子中的篮子满了,必须被停止下来。
下面看我们很这个比较长的程序模拟:
public class ProducerConsumer { public static void main(String[] args) { /*来我们开始模拟*/ SyncStack ss = new SyncStack(); Producer p = new Producer(ss); Consumer c = new Consumer(ss); new Thread(p).start();//就干脆不起名了,直接start,生产者线程start //new Thread(p).start();//第二个生产者 new Thread(c).start();//消费者线程start, } } class Producer implements Runnable{//它是个线程喔,因为好多个生产者一起在执行 SyncStack ss = null;//因为它要往篮子里面加馒头,所以要有个篮子的引用 Producer(SyncStack ss) {//生产者的构造方法,作为一个producer,你生产的时候总要知道你要往哪个篮子里扔吧。 this.ss = ss; } public void run() { //线程的run方法就是生产窝头,这里设定每个人最多生产20个 for(int i = 0 ; i < 20 ; i ++ ) { WoTou wt = new WoTou(i); ss.push(wt); System.out.println("生产了 :" + wt);//生产一个就打一个出来 try { Thread.sleep((int)Math.random()*1000);//每生产一个睡一下 } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer implements Runnable { //消费者,也是个线程 SyncStack ss = null;//因为它往篮子里面拿馒头,所以要有个篮子的引用 Consumer(SyncStack ss) {//消费者的构造方法,作为一个消费者,你消费的时候总要知道你要往哪个篮子里拿吧。 this.ss = ss; } public void run() { //线程的run方法就是拿窝头,这里设定每个人最多拿20个 for(int i = 0 ; i < 20 ; i ++ ) { WoTou wt = ss.pop(); System.out.println("消费了 :" + wt); try { Thread.sleep((int)Math.random()*2);//每消费一个睡一下 ,注意这里设定消费比生产快,如果没有这个wait机制的话篮子就会空 } catch (InterruptedException e) { e.printStackTrace(); } } } } class WoTou { int id;//每个馒头打一个几号 WoTou(int id) { this.id = id; } public String toString() { return "WoTou : "+id; } } class SyncStack {//框,有个要求是先进去的后拿出来,所以拿栈(一种数据机构,先进后出) int index = 0;//装到第几个了 WoTou[] arrayWt = new WoTou[6];//用个wotou数组来装。 public synchronized void push(WoTou wt) {//往里面扔窝头的方法 while(index == arrayWt.length) { //这里用while,为什么不能用if呢?你想啊,如果满了的话,你进入if里面,但万一这时候又catch到了 //exception,那么就会跳过if,然后notify了,所以不能用if try { this.wait();//用Object的wait方法 } catch (InterruptedException e) { e.printStackTrace(); } } this.notifyAll();//没这个的话消费者就会一直wait无法唤醒,结果就会等它生产到篮子满了之后,它也wait,然后程序死锁…… arrayWt[index] = wt; index ++; } public synchronized WoTou pop() { //如果不同步的话会很多错误。 while(index == 0) { try { this.wait(); //它一wait,一来线程阻碍,二来会抛掉锁 } catch (InterruptedException e) { e.printStackTrace(); } } this.notifyAll(); index --; return arrayWt[index]; } }
注意
wait与notify是Java同步机制中的重要组成部分。结合与synchronized关键字使用,可以建立很多优秀的同步模型,例如生产者-消费者模型。但是在使用wait()、notify()、notifyAll()函数的时候,需要特别注意以下几点:
wait()、notify()、notifyAll()方法不属于Thread类,而是属于Object基础类,也就是说每个对象都有wait()、notify()、notifyAll()的功能。因为每个对象都有锁,锁是每个对象的基础,因此操作锁的方法也是最基础的。
调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj){...} 代码段内。
调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj){...} 代码段内唤醒线程A。
当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。
如果线程A1,A2,A3都在obj.wait(),则线程B调用obj.notify()只能唤醒线程A1,A2,A3中的一个(具体哪一个由JVM决定)。
如果线程B调用obj.notifyAll()则能全部唤醒等待的线程A1,A2,A3,但是等待的线程要继续执行obj.wait()的下一条语句,必须获得obj锁。因此,线程A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。
当线程B调用obj.notify()或者obj.notifyAll()的时候,线程B正持有obj锁,因此,线程A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到线程B退出synchronized代码块,释放obj锁后,线程A1,A2,A3中的一个才有机会获得对象锁并得以继续执行。