• 生产者消费者问题


    先抛出一个例子:

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

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

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

  • 相关阅读:
    GISer面对创业的困惑
    近期微博吐槽言论存档,涉及“性能优化”、C++陋习等
    HDU 2825 Wireless Password【AC自动机+DP】
    20130809, 微软八月安全补丁提前通知
    终于把3DMAX的MSE搞定了!
    UVA 11464 Even Parity (独特思路)
    [置顶] hdu 4418 高斯消元解方程求期望
    UVA 10652 Board Wrapping
    少儿编程-教育:少儿编程教育
    少儿编程:目录
  • 原文地址:https://www.cnblogs.com/wangshen31/p/6875615.html
Copyright © 2020-2023  润新知