9. 多线程 Part 4 生产者及消费者模式 --- 学习笔记
来源:互联网 发布:淘宝怎么更换支付宝 编辑:程序博客网 时间:2024/06/08 11:00
9.7 线程操作案例 --- 生产者及消费者
在线程操作中有一个经典的案例程序, 即生产者和消费者问题 ,生产者不断生产;消费者不断取走生产者生产的产品。 生产者生产出信息后将其放到一个区域之中,消费者从此区域中取出数据 ,但是在本程序中因为牵涉到线程运行的不确定性,所以会存在以下两点问题:
- 假设生产者线程刚向数据存储空间添加了信息的名称,还没有加入该信息的内容, 程序就切换到了消费者线程 ;消费者线程将把信息的名称和上一个信息的内容联系到一起。
- 生产者放入了若干次的数据,消费者才开始取数据; 或者是, 消费者取完一个数据后,还没有等到生产者放入新的数据,又重复取出已取出过的数据。
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 本章要点
- 线程(Thread)是指程序的运行流程。多线程机制可以同时运行多个程序块,使程序运行的效率更高,也解决了传统程序设计语言所无法解决的问题。
- 如果要在类中激活线程,必须先做好下面两项准备:(1)此类必须是扩展自Thread类,是自己成为他的子类。(2)线程的处理必须覆写在run()方法内。
- run()方法是定义在Thread类中的一种方法,因此把线程的程序代码编写在run()方法内所做的就是覆写的操作。
- Runnable接口中声明了抽象的run()方法,因此必须在实现Runnable接口的类中明确定义run()方法。
- 在每一个线程创建和消亡之前,均会处于创建、就绪、运行、阻塞、终止状态之一。
- 暂停状态的线程可以由下列情况产生:(1)该线程调用对象的wait()方法时; (2)该线程本身调用Thread的sleep()方法时; (3)该线程和另外一个线程join()在一起时。
- 被冻结因素消失的原因有以下两种情况: (1) 如果线程是由调用对象的wait()方法冻结,则该对象的notify()方法或notifyAll()方法被调用时可以解除冻结。; (2)线程进入休眠sleep()状态,但指定的休眠时间到了。 也会解除冻结。
- 当线程的run()方法运行结束,或是由线程调用其stop()方法时,线程进入消亡状态。
- Thread类中的sleep()方法可以用来控制线程的休眠状态,休眠的时间要视sleep()中的参数而定。
- 要强制某一线程运行,可以join()方法。
- join()方法会抛出InterruptedException异常,所以编写时必须把join()方法编写在try...catch语句块内。
- 当多个线程对象操控同一共享资源时,要使用synchronized关键字来进行资源的同步处理。
9.10 习题
- 设计4个线程对象,两个线程执行减操作,两个线程执行加操作。
- 设计一个生产电脑和搬运电脑类,要求生产出一台电脑就搬走一台电脑,如果没有新的电脑生产出来,则搬运工要等待新电脑产出;如果生产出的电脑没有伴奏,则要等待电脑搬走之后再生产,并统计处生产的电脑数量。
9.11 知识补充:
- notify()、wait()的用法!
- 线程被唤醒后,从何处开始继续执行:是从wait()处? 还是重新从run()开始?
- 可以用this.wait()和this.notify()来控制线程的等待和唤醒么????实验实验!!!
- 线程的唤醒和等待机制
- 9. 多线程 Part 4 生产者及消费者模式 --- 学习笔记
- 多线程学习之生产者消费者模式学习笔记
- 第十七节多线程及生产者消费者模式
- 多线程生产者消费者模式
- 生产者消费者模式 多线程
- 多线程-生产者消费者模式
- java多线程 ----生产者消费者模式
- Java多线程 --- 生产者消费者模式
- Java多线程 生产者消费者模式
- 多线程应用--生产者、消费者模式
- java多线程---生产者消费者模式
- 多线程之生产者消费者模式
- 多线程编程:生产者消费者模式
- java 多线程 生产者消费者模式
- 多线程之生产者-消费者模式
- 【Java多线程】生产者消费者模式
- JAVA多线程(三)生产者消费者模式及实现方法
- Java多线程:生产者及消费者
- andrid定义Button的样式和选中样式
- selenium处理 <a target=_blank 几种方式整理
- LeetCode: Best Time to Buy and Sell Stock
- 用apktool和dex2jar反编译
- 正则表达式详解
- 9. 多线程 Part 4 生产者及消费者模式 --- 学习笔记
- JAVA的动态代理
- 访谈录它的重要性
- VS2005下编译openssl
- 嵌入式Linux系统实现3G网卡拨号
- android摄像头编程心得
- Spring框架中配置数据库
- 简单记录一次ORA-00600 kcratr_nab_less_than_odr
- selenium问题:Link has target '_blank', which is not supported in Selenium!