Java多线程操作——生产者与消费者

来源:互联网 发布:关系图算法 编辑:程序博客网 时间:2024/06/05 15:19

实例要求:

 在线程操作中有一个经典的案例程序——生产者和消费者问题,生产者不断生产,消费者不断取走生产者生产的产品。

在图中非常清楚的表示出,生产者生产出信息后将其放到一个区域中,那么,就可以将生产者和消费者两个线程通过信息类联合在一起。
class Info{// 定义信息类private String name = "李兴华"; // 定义name属性private String content = "JAVA讲师"  ;// 定义content属性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 ;}};class Producer implements Runnable{// 通过Runnable实现多线程private Info info = null ;// 保存Info引用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("李兴华") ;// 设置名称try{Thread.sleep(90) ;}catch(InterruptedException e){e.printStackTrace() ;}this.info.setContent("JAVA讲师") ;// 设置内容flag = false ;}else{this.info.setName("mldn") ;// 设置名称try{Thread.sleep(90) ;}catch(InterruptedException e){e.printStackTrace() ;}this.info.setContent("www.mldnjava.cn") ;// 设置内容flag = true ;}}}};class Consumer implements Runnable{private Info info = null ;public Consumer(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() + " --> " + this.info.getContent()) ;}}};public class ThreadCaseDemo01{public static void main(String args[]){Info info = new Info();// 实例化Info对象Producer pro = new Producer(info) ;// 生产者Consumer con = new Consumer(info) ;// 消费者new Thread(pro).start() ;new Thread(con).start() ;}};


以上的代码将之前的两个问题全部暴漏出来。
之所以会出现内容不对应的情况,是因为中间加入了延迟操作,所以有可能产生不同步的问题,那么此时可以使用同步解决设置内容的问题。
class Info{// 定义信息类private String name = "李兴华"; // 定义name属性private String content = "JAVA讲师"  ;// 定义content属性public synchronized void set(String name,String content){this.setName(name) ;// 设置名称try{Thread.sleep(300) ;}catch(InterruptedException e){e.printStackTrace() ;}this.setContent(content) ;// 设置内容}public synchronized void get(){try{Thread.sleep(300) ;}catch(InterruptedException e){e.printStackTrace() ;}System.out.println(this.getName() + " --> " + this.getContent()) ;}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 ;}};class Producer implements Runnable{// 通过Runnable实现多线程private Info info = null ;// 保存Info引用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("李兴华","JAVA讲师") ;// 设置名称flag = false ;}else{this.info.set("mldn","www.mldnjava.cn") ;// 设置名称flag = true ;}}}};class Consumer implements Runnable{private Info info = null ;public Consumer(Info info){this.info = info ;}public void run(){for(int i=0;i<50;i++){this.info.get() ;}}};public class ThreadCaseDemo02{public static void main(String args[]){Info info = new Info();// 实例化Info对象Producer pro = new Producer(info) ;// 生产者Consumer con = new Consumer(info) ;// 消费者new Thread(pro).start() ;new Thread(con).start() ;}};

以上代码解决了数据的完整性问题,但是依然存在重复取的问题,既然有重复取则肯定有重复设置的问题。
并没有达到,设置一个取走一个的功能要求。
如果想采用以上的一种机制,则必须依靠Object类中的方法支持。

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

Object类是所有类的父类,在此类中有以下几个方法是对线程操作有所支持的。
1、 public final void wait() throws InterruptedException    普通类型  用于线程等待。
2、 public final wait(long timeout) throws InterruptedException  普通类型 用于线程等待,并指定等待的最长时间,以毫秒为单位。
3、 public final void wait(long timeout,int nanos) throws InterruptedException 普通类型,用于线程等待,并指定最长毫秒以及纳秒。
4、 public final void notify() 普通类型 用于唤醒第一个等待的线程。
5、 public final void notifyAll() 普通类型 用于唤醒全部等待的线程。

notify()和notifyAll() 
对于唤醒的操作有两个:notify()、notifyAll()。一般来说,所有等待的线程都会按照顺序进行排列,如果现在使用了notify()方法法人话,则会唤醒第一个等待的线程执行,而如果使用了notifyAll()方法,则会唤醒所有等待的线程,哪个线程优先级高,哪个线程就有可能先执行。

问题解决2—— 加入等待与唤醒、
如果要想生产者不重复生产,消费者不重复取走,则可以增加一个标志位,假设标志位boolean型变量,如果标志位的内容为true,则表示可以生产,但是不能取走,如果此时线程执行到了消费者线程则应该等待,如果标志位的内容为false,则表示可以取走,但是不能生产,如果生产者线程运行。则应该等待,如下图所示:

直接修改info类。增加等待和唤醒机制。
class Info{// 定义信息类private String name = "李兴华"; // 定义name属性private String content = "JAVA讲师"  ;// 定义content属性private boolean flag = true ;// 设置标志位public synchronized void set(String name,String content){if(!flag){try{super.wait() ;}catch(InterruptedException e){e.printStackTrace() ;}}this.setName(name) ;// 设置名称try{Thread.sleep(300) ;}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(300) ;}catch(InterruptedException e){e.printStackTrace() ;}System.out.println(this.getName() + " --> " + this.getContent()) ;flag  = true ;// 改变标志位,表示可以生产super.notify() ;}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 ;}};class Producer implements Runnable{// 通过Runnable实现多线程private Info info = null ;// 保存Info引用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("李兴华","JAVA讲师") ;// 设置名称flag = false ;}else{this.info.set("mldn","www.mldnjava.cn") ;// 设置名称flag = true ;}}}};class Consumer implements Runnable{private Info info = null ;public Consumer(Info info){this.info = info ;}public void run(){for(int i=0;i<50;i++){this.info.get() ;}}};public class ThreadCaseDemo03{public static void main(String args[]){Info info = new Info();// 实例化Info对象Producer pro = new Producer(info) ;// 生产者Consumer con = new Consumer(info) ;// 消费者new Thread(pro).start() ;new Thread(con).start() ;}};

此时,才算真正的完成了生产者和消费者的正确操作。

总结:
1、在本程序操作中需要以下两点问题:
    生产者要不断生产,但是不能生产错误的信息或者是重复生产。
    消费者要不断取走,但是不能重复取走。
2、Object类中对线程的支持。
   等待:wait() 方法。
   唤醒:notify()、notifyAll()方法。


0 0
原创粉丝点击