9. 多线程 Part 4 生产者及消费者模式 --- 学习笔记

来源:互联网 发布:淘宝怎么更换支付宝 编辑:程序博客网 时间:2024/06/08 11:00

9.7 线程操作案例  ---  生产者及消费者

                在线程操作中有一个经典的案例程序, 即生产者和消费者问题 ,生产者不断生产;消费者不断取走生产者生产的产品。 生产者生产出信息后将其放到一个区域之中,消费者从此区域中取出数据 ,但是在本程序中因为牵涉到线程运行的不确定性,所以会存在以下两点问题:

  1. 假设生产者线程刚向数据存储空间添加了信息的名称,还没有加入该信息的内容, 程序就切换到了消费者线程 ;消费者线程将把信息的名称和上一个信息的内容联系到一起。
  2. 生产者放入了若干次的数据,消费者才开始取数据; 或者是, 消费者取完一个数据后,还没有等到生产者放入新的数据,又重复取出已取出过的数据。

     9.7.1 程序的基本实现

                  因为程序中生产者不断生产的是信息,而消费者不断取出的也是信息, 所以定义一个保存信息的类 Info.java。 代码如下 :   要理解为什么此类不需要构造方法

class Info{    private String name;     //private String name = "forfan06";    private int age;         //private int age = 27;    /*    -------------------------------    public info(String name, int age){        this.name = name;        this.age = age;    }    -----------------------------    */    public void setName(String name){        this.name = name;    }    public String getName(){        return this.name;    }    public void setAge(int age){        this.age = age;    }    public int getAge(){        return this.age;    }}

              Info类只包含了保存信息名称的 name属性和age属性,因为生产者和消费者要操作同一个空间的内容,所以生产者和消费者分别实现Runnable接口,并接收Info类的应用。类生产者和消费者的代码如下

class Producer implements Runnable{    private Info info = null;    public Producer(Info info){        this.info = info;    }    public void run(){        boolean flag = false;        for (int i = 0; i < 50; i++){            if(flag){                this.info.setName("User" + i);                try{                    Thread.sleep(90);                }catch(InterruptedException e){                    e.printStackTrace();                }                this.info.setAge(i*2-1);                flag = false;            }else{                this.info.setName("Customer" + i);                try{                    Thread.sleep(90);                }catch(InterruptedException e){                    e.printStackTrace();                }                this.info.setAge(i*2);                flag = true;            }        }    }}

               在生产者、和消费者的构造方法中传入了Info类的实例化对象,然后在run()方法中循环了50次以产生信息的具体内容。 为了更容易发现以上列出的两个问题,在本程序中设置信息名称和内容的地方加入了延迟操作Threa.sleep();

class Customer implements Runnable{    private Info info = null;    public Customer(Info info){        this.info = info;    }    public void run(){        for (int i = 0; i < 50; i++){            try{                Thread.sleep(90);            }catch(InterruptedException e){                e.printStackTrace();            }            System.out.println(this.info.getName() + ", info: " + this.info.getAge() + "!!!!");        }    }}

测试程序:

public class ThreadCaseDemo01{    public static void main(String args[]){        Info info = new Info();        Producer p1 = new Producer(info);        Customer c1 = new Customer(info);        new Thread(p1, "Producer").start();        new Thread(c1, "Customer").start();    }}


运行结果截图:

User41, info: 80!!!!Customer42, info: 81!!!!User43, info: 84!!!!Customer44, info: 85!!!!User45, info: 88!!!!...Customer46, info: 89!!!!User47, info: 92!!!!Customer48, info: 93!!!!User49, info: 96!!!!User49, info: 97!!!!


     9.7.2 问题解决1  --- 加入同步

          如果要为操作加入同步,则可以通过定义同步方法的方式完成,即将设置名称和姓名定义成一个方法,代码如下所示:

Info类

class Info{    private String name = "forfan06";    private String content = "WHPU";    public synchronized void set(String name, String content){        this.setName(name);        try{            Thread.sleep(3);        }catch(InterruptedException e){            e.printStackTrace();        }        this.setContent(content);    }    public synchronized void get(){        try{            Thread.sleep(10);        }catch(InterruptedException e){            e.printStackTrace();        }        System.out.println(this.getName() + "--->" + this.getContent());    }    public String getContent(){        return this.content;    }    public String getName(){        return this.name;    }    public void setName(String name){        this.name = name;    }    public void setContent(String content){        this.content = content;    }}
以上类中定义了一个set()和get()方法,并且都使用了synchronized关键字进行声明,因为现在不希望直接调用getter()和setter()方法,所以修改生产者和消费者代码如下:

Producer类

class Producer implements Runnable{    private Info info = null;    public Producer(Info info){        this.info = info;    }    public void run(){        boolean flag = false;        for(int i = 0; i < 50; i++){            if(flag){                this.info.set("forfan06", "WHPU");                flag = false;            }else{                this.info.set("Dylan", "CSDN");                flag = true;            }        }    }}

Customer类

class Customer implements Runnable{    private Info info = null;    public Customer(Info info){        this.info = info;    }    public void run(){        for(int i = 0; i < 50; i++){            try{                Thread.sleep(1);            }catch(InterruptedException e){                e.printStackTrace();            }            this.info.get();        }    }}

测试类

public class ThreadCaseDemo02{    public static void main(String args[]){        Info info = new Info();        Producer p = new Producer(info);        Customer c = new Customer(info);        new Thread(p, "Set").start();        new Thread(c, "Get").start();    }}

部分输出结果:

Dylan--->CSDNforfan06--->WHPUDylan--->CSDNforfan06--->WHPUforfan06--->WHPUforfan06--->WHPU


从程序运行的结果可以发现,信息错乱的问题解决了,但是依然存在重复读取的问题,既然有重复读取,肯定会有重复设置的问题,那么对于这样的的问题该如何解决呢??

此时就需要使用Object类。


     9.7.3 Object类对线程的支持 ---  等待与唤醒

           Object类是所有类的父类,在此类中有以下几种方法时对线程操作有所支持的,如下表所示:

                           

      可以将一个线程设置为等待状态,但是对于唤醒的操作有两个,分别为notify()、notifyAll()。 一般来说,所有等待的线程会按顺序进行排列,如果现在使用了notify()方法,则会唤醒第1个等待的线程执行;而如果使用了notifyAll()方法,则会唤醒所有的等待线程,哪个线程的优先级高,哪个线程就有可能先执行。但是还是要看CPU资源被谁先抢到!!


     9.7.4 问题解决 2 --- 加入等待与唤醒

         如果想让生产者不重复生产,消费者不重复取走,可以增加一个标志位,假设标志位为boolean型变量,如果标志位的内容为true,则表示可以生产,但是不能去走,此时线程执行到消费者线程则应该等待; 如果标志位的内容为false,则表示可以取走,但是不能生产,如果生产者线程运行,则应该等待。 操作流程如下图所示:


    要完成以上功能,直接修改Info类即可,在Info类中加入标志位,并通过判断标志位完成等待与唤醒操作。代码如下

Info类

class Info{    private String name = "forfan06";    private String content = "WHPU";    private boolean flag = false;    public void setName(String name){        this.name = name;    }    public void setContent(String content){        this.content = content;    }    public String getName(){        return this.name;    }    public String getContent(){        return this.content;    }    public synchronized void set(String name, String content){         if(!flag){              try{                   super.wait();              }catch(InterruptedException e){                   e.printStackTrace();              }         }         this.setName(name);         try{              Thread.sleep(3);         }catch(InterruptedException e){              e.printStackTrace();         }         this.setContent(content);         flag = false;         super.notify();    }    public synchronized void get(){        if(flag){            try{                super.wait();            }catch(InterruptedException e){                e.printStackTrace();            }        }        try{            Thread.sleep(10);        }catch(InterruptedException e){            e.printStackTrace();        }        System.out.println(this.getName() + "--->" + this.getContent());        flag = true;        super.notify();    }}

Producer类

class Producer implements Runnable{    private Info info = null;    public Producer(Info info){        this.info = info;    }    public void run(){        boolean flag = false;        for(int i = 0; i < 50; i++){            if(flag){                this.info.set("forfan06", "WHPU");                flag = false;            }else{                this.info.set("Dylan", "CSDN");                flag = true;            }        }    }}

Customer类

class Customer implements Runnable{    private Info info = null;    public Customer(Info info){        this.info = info;    }    public void run(){        for(int i = 0; i < 50; i++){            try{                Thread.sleep(1);            }catch(InterruptedException e){                e.printStackTrace();            }            this.info.get();        }    }}

测试类

public class ThreadCaseDemo03{    public static void main(String args[]){        Info info = new Info();        Producer p = new Producer(info);        Customer c = new Customer(info);        new Thread(p, "Set").start();        new Thread(c, "Get").start();    }}


9.8 线程的生命周期

     在java中一个线程对象有自己的生命周期,如果要控制好线程的生命周期,则首先应认识其生命周期,如图所示:

                                  

          上图中大部分的线程生命周期方法都已经学过,其中的3个新方法介绍如下:

  • suspend()方法: 暂时挂起线程
  • resume()方法:恢复挂起的线程
  • stop()方法:停止线程

       **********但是对于线程中suspend()、resume()、stop()3种方法并不推荐使用,因为这3种方法在操作时会产生死锁的问题**********

注意: suspend()、resume()、stop()方法都使用了@Deprecated声明!!!!@Deprecated属于Annotation的语法,表示此操作不建议使用。一旦使用了这些方法将出现警告信息。

        既然以上3种方法都不推荐使用那么该如何停止一个线程的执行呢????在多线程的开发中可以通过设置标志位的方法停止一个线程的运行,代码如下:

class MyThread implements Runnable{    private boolean flag = true;            //定义标志位属性    public void run(){                      //覆写run()方法        int i = 0;        while (this.flag){            while(true){                System.out.println(Thread.currentThread().getName() + "运行, i =  " + (i++));            }        }    }    public void stop(){                       //编写停止方法        this.flag = false;                    //修改标志位    }}public class StopDemo{    public static void main(String agrs[]){        MyThread my = new MyThread();        Thread t = new Thread(my, "线程");        t.start();        my.stop();                        //线程停止,修改标志位    }}

   以上程序一旦调用stop()方法就会将MyThread类中的flag变量设置为false,这样run()方法就会停止运行,这种停止方式是开发中比较常用的。


9.9  本章要点

  1. 线程(Thread)是指程序的运行流程。多线程机制可以同时运行多个程序块,使程序运行的效率更高,也解决了传统程序设计语言所无法解决的问题。
  2. 如果要在类中激活线程,必须先做好下面两项准备:(1)此类必须是扩展自Thread类,是自己成为他的子类。(2)线程的处理必须覆写在run()方法内。
  3. run()方法是定义在Thread类中的一种方法,因此把线程的程序代码编写在run()方法内所做的就是覆写的操作。
  4. Runnable接口中声明了抽象的run()方法,因此必须在实现Runnable接口的类中明确定义run()方法。
  5. 在每一个线程创建和消亡之前,均会处于创建、就绪、运行、阻塞、终止状态之一。
  6. 暂停状态的线程可以由下列情况产生:(1)该线程调用对象的wait()方法时; (2)该线程本身调用Thread的sleep()方法时; (3)该线程和另外一个线程join()在一起时。
  7. 被冻结因素消失的原因有以下两种情况: (1) 如果线程是由调用对象的wait()方法冻结,则该对象的notify()方法或notifyAll()方法被调用时可以解除冻结。; (2)线程进入休眠sleep()状态,但指定的休眠时间到了。 也会解除冻结。
  8. 当线程的run()方法运行结束,或是由线程调用其stop()方法时,线程进入消亡状态。
  9. Thread类中的sleep()方法可以用来控制线程的休眠状态,休眠的时间要视sleep()中的参数而定。
  10. 要强制某一线程运行,可以join()方法。
  11. join()方法会抛出InterruptedException异常,所以编写时必须把join()方法编写在try...catch语句块内。
  12. 当多个线程对象操控同一共享资源时,要使用synchronized关键字来进行资源的同步处理。


9.10  习题

  1. 设计4个线程对象,两个线程执行减操作,两个线程执行加操作。
  2. 设计一个生产电脑和搬运电脑类,要求生产出一台电脑就搬走一台电脑,如果没有新的电脑生产出来,则搬运工要等待新电脑产出;如果生产出的电脑没有伴奏,则要等待电脑搬走之后再生产,并统计处生产的电脑数量。


9.11  知识补充:

  1. notify()、wait()的用法! 
  2. 线程被唤醒后,从何处开始继续执行:是从wait()处? 还是重新从run()开始?  
  3. 可以用this.wait()和this.notify()来控制线程的等待和唤醒么????实验实验!!!
  4. 线程的唤醒和等待机制

0 0
原创粉丝点击