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

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--->CSDN
forfan06--->WHPU
Dylan--->CSDN
forfan06--->WHPU
forfan06--->WHPU
forfan06--->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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值