zoukankan      html  css  js  c++  java
  • java线程(2)——模拟生产者与消费者

    前言:

           我们都听说过生产者和消费者的例子吧,现在来模拟一下。生产者生产面包,消费者消费面包。假定生产者将生成出来的面包放入篮子中,消费者从篮子中取。这样,当篮子中没有面包时,消费者不能取。当篮子满了以后,消费者不能一直生产。


    思考:

           使用面向对象的思想进行分析,涉及的事物有:生产者、消费者、篮子和面包。两个线程有:生产者的生产行为、消费者的消费行为。

    Bread面包类

    class Bread{
    	int id;
    	Bread(int id){
    		this.id=id;
    	}
    	public String toString(){
    		return "Bread:"+id;
    	}
    }

    SyncStack篮子类

    在篮子中,一般先放入的面包最后才取出,因此这里可以模拟数据结构栈的操作,提供了两个方法push()和pop()。

    这里,假定篮子的最大容量为6个面包。

    class SyncStack{
    	int index=0;
    	Bread[] arrWT=new Bread[6];
    	
    	//1、放入
    	public synchronized void push(Bread wt){
    		//装满了
    		//if(index==arrWT.length){  防止发生异常之后,篮子满了仍然生成出现问题
    		while(index==arrWT.length){
    			try {
    				this.wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		//叫醒wait中的线程
    		this.notify();
    		//this.notifyAll(); //叫醒所有
    		arrWT[index]=wt;
    		index++;
    	}
    	
    	//2、拿出
    	public synchronized Bread pop(){
    		//篮子空了
    		while(index==0){
    			try {
    				this.wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		this.notify();
    		index--;
    		return arrWT[index];
    	};
    }

    生产者和消费者

        终于轮到线程类出场了!生产者Producer实现如下,消费者Consumer类也十分类似,只是不再具备生产面包的功能,并且将push方法换成了pop,在这里就不展示了。

    class Producer implements Runnable{
    //持有框的引用
    	SyncStack ss=null;
    	Producer(SyncStack ss){
    		this.ss=ss;
    	}
    	@Override
    	public void run() {
    		for (int i = 1; i < 20; i++) {
    			Bread wt=new Bread(i);
    			//扔到框里
    			ss.push(wt);
    			System.out.println("producer:"+wt);
    			try {
    				Thread.sleep((long) (Math.random()*1000));
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		
    	}
    	
    }


    测试:

    这里我们简单的测试一下,一个生产者和一个消费者。

    public static void main(String[] args) {
    		SyncStack ss=new SyncStack();
    		Producer p=new Producer(ss);
    		Consumer c=new Consumer(ss);
    		
    		new Thread(p).start();
    		new Thread(c).start();
    	}


    执行结果:

    基本符合前面的要求


    分析:


    1、synchronized

    一种同步锁,可以修饰代码块、方法等。当一个线程访问其修饰的内容时,视图访问该对象的线程将被阻塞。举个小例子:

    /**
     * 获得当前时间
     */
    	public synchronized static void getTime() {
    		System.out.println("1、进入获取时间方法=====");
    		Date date = new Date();
    		DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    		String time = format.format(date);
    		System.out.println("2、"+Thread.currentThread().getName() + ":" + time);
    		System.out.println("3、获取时间成功=====");
    		System.out.println();
    	}
    


    我们启100次线程去调用他,在没有加synchronized关键字时,截取部分打印结果如下图:





    加上关键字之后,打印结果如下:



           可以很容易看出,在该方法前加上synchronized关键字之后,其他线程如果想继续访问,就会被阻塞,等待该线程执行结束后,其他线程才可以继续访问。

          在上面消费者和生成者的例子中,我们在SyncStack的push和pop方法前都加上了synchronized关键字,就是为了保证“放”和“取”的原子性,这样模拟的结果才是合理的。

           那么有人可能会问,生产者生产馒头和消费者消费馒头是否需要保持原子性,同一时间只允许一个线程执行呢?结合实际情况,可能有多个生产者、消费者同时进行操作,因此,这里不应做限制。


    2、sleep()和wait()

           在上面的例子中,Producer和Consumer内部使用的Thread.sleep()方法,而在SyncStack中的push和pop中却是用的wait()方法。这两个方法看似功能类似,有什么差别呢?

           首先,翻到他们的根去看定义。注释看不懂可以先不看,不过注释就把他们的区别都说明白了。


    wait()

    public class Object {
    
     /**
         * Causes the current thread to wait until another thread invokes the
         * {@link java.lang.Object#notify()} method or the
         * {@link java.lang.Object#notifyAll()} method for this object.
         * In other words, this method behaves exactly as if it simply
         * performs the call {@code wait(0)}.
         * <p>
         * The current thread must own this object's monitor.
    	*/
         public final void wait() throws InterruptedException {
        <span style="white-space:pre">	wait(0);</span>
        }
    }

    sleep()

    public
    class Thread implements Runnable {
     /**
         * Causes the currently executing thread to sleep (temporarily cease
         * execution) for the specified number of milliseconds, subject to
         * the precision and accuracy of system timers and schedulers. The thread
         * does not lose ownership of any monitors.
         */
     public static native void sleep(long millis) throws InterruptedException;
     }

          

          

          先说点肤浅的差别:

          wait()是Object类中的方法,sleep()是Thread类中的。sleep必须指明挂起或睡眠的时间,而wait不需要。

          

          上面注释中说的:

           sleep()方法——占着CPU睡觉:CPU部分资源别占用,其他线程无法进入,但并不会失去他的监控,等到指定时间到了以后,继续恢复执行。

           wait()  方法——等待使用CPU:线程会放弃对象锁,直到收到notify()通知才会进入准备状态。


           我的理解:

           在该实例中,push和pop中在“篮子空了”、“篮子装满了”的情况下使用了wait()方法。此时,已经无法继续工作下去,需要让出CPU给其他操作,才能保证生产-消费线进行。等到"篮子中有面包"情况时,调用notify通知线程执行。

           Producer和Consumer中的sleep()方法是为了让放大彼此交替的差距,来接近现实生活的状态。

          

  • 相关阅读:
    Kubernetes(k8s)中namespace的作用、反向代理访问k8s中的应用、k8s监控服务heapster
    Kubernetes(k8s)中dashboard的部署。
    Kubernetes(k8s)中Pod资源的健康检查
    Kubernetes(k8s)安装dns附件组件以及使用
    Kubernetes(k8s)的deployment资源
    Kubernetes(k8s)的Service资源
    Kubernetes(k8s)的RC(Replication Controller)副本控制器
    Kubernetes(k8s)常用资源的使用、Pod的常用操作
    简易图书管理系统(主要是jsp+servlet的练习),基于jsp+servlet的图书管理系统
    js 提取 sql 条件 表名 limit
  • 原文地址:https://www.cnblogs.com/saixing/p/6730223.html
Copyright © 2011-2022 走看看