生产者消费者问题

先抛出一个例子:

现在我们有一个馒头的例子。有做馒头的师傅,有个篮子,也有吃馒头的人。然后我们要用程序来模拟这个东西。              

这是篮子的图示,其中因为篮子有个特点就是后放进去的先拿出来,陷进去的后拿出来,所以决定用数据结构栈来模拟它,这是一种据说也是先进后出的数据结构喔。

那么篮子满了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中的一个才有机会获得对象锁并得以继续执行。

原文地址:https://www.cnblogs.com/wangshen31/p/6875615.html