生产者与消费者(多线程经典案例)

注:此示例来自MLDN讲师李兴华JAVA SE基础教学部分

生产者和消费者是多线程中一个经典的操作案例下面一起看下代码:

示例一:

package org.lx.multithreading;

/**
 * 定义一个信息类
 * 
@author Solitary
 
*/
class Info {    
    
private String name = "罗星" ;            //定义name属性        
    private String content ="JAVA初学者";    //定义content属性
    
    
//getter     setter
    public String getName() {
        
return name;
    }
    
public void setName(String name) {
        
this.name = name;
    }
    
public String getContent() {
        
return content;
    }
    
public void setContent(String content) {
        
this.content = content;
    }
} ;

/**
 * 定义生产者
 * 
@author Solitary
 
*/
class Producer implements Runnable {    //通过接口Runnable实现多线程
    private Info info = null ;            //保存Info引用
    
    
public Producer(Info info) {        //通过构造方法传递引用
        this.info = info ;
    }
    
    @Override
    
public void run() {
        
boolean flag = false ;                //定义标志位            
        for(int i = 0; i < 50; i++) {        //生产50次信息
            if(flag){    //如果标志位为true 将设置 中文内容
                this.info.setName("小星") ;    //设置名字
                try {
                    
//为了更好的体现代码运行效果在设置姓名和内容之间加入延迟操作
                    Thread.sleep(300) ;
                } 
catch (InterruptedException e) {        //线程被打断后会抛出此异常
                    e.printStackTrace();
                }            
                
this.info.setContent("JAVA初学者") ;        //设置内容
                flag = false ;    //改变标志位,用于变换输入内容
                
            }
else{    //如果标志位为false 将设置英文内容        
                this.info.setName("Solitary") ;    //设置名字
                try {
                    
//为了更好的体现代码运行效果在设置姓名和内容之间加入延迟操作
                    Thread.sleep(300) ;
                } 
catch (InterruptedException e) {        //线程被打断后会抛出此异常
                    e.printStackTrace();
                }            
                
this.info.setContent("Coder") ;        //设置内容
                flag = true ;    //改变标志位,用于变换输入内容
            }
        }
    }    
    
} ;

/**
 * 定义消费者
 * 
@author Solitary
 
*/
class Consumer implements Runnable {
    
private Info info = null ;        //用于保存Info引用,其目的是为了让消费者和生产者拥有同一个info
    
    
public Consumer(Info info){
        
this.info = info ;
    }
    
public void run() {
        
for(int i = 0; i < 50; i++) {        //消费和也从info中取50次消息
            try {
                Thread.sleep(
300) ;
            } 
catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(
this.info.getName() + 
                    
" --> " + this.info.getContent()) ;
        }
    }
} ;

/**
 * 测试代码
 * 
@author Solitary\
 
*/
public class MultiThreadingDemo01 {
    
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() ;        //启动线程        
    }
}
示例一(执行效果):
小星 --> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
Solitary 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> JAVA初学者
小星 
--> JAVA初学者
Solitary 
--> Coder
Solitary 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> Coder
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
Solitary 
--> JAVA初学者
小星 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
Solitary 
--> JAVA初学者
Solitary 
--> JAVA初学者
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> JAVA初学者
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> JAVA初学者
小星 
--> JAVA初学者
请注意运行结果,为什么我代码里面明明成对设置的是:小星 --> JAVA初学者;  Solitary --> Coder而运行结果确实有不匹配的呢?
分析:
因为生产者和消费者的线程都已启动,那么不能保证谁在前,或者谁在后,在生产者还在设置内容的时候(比如:已经设置好的Info的name=小星,Context=JAVA初学者,而此时生产者又设置了name = Solitary正打算设置Content = Coder),而消费者已经取走了内容,那么显示的肯定就是Solitary --> JAVA初学者这样的结果,因为两个线程都在这执行着,出现了不匹配的结果。可以将代码修改为:

示例二

package org.lx.multithreading;

/**
 * 定义一个信息类
 * 
@author Solitary
 
*/
class Info {    
    
private String name = "罗星" ;            //定义name属性        
    private String content ="JAVA初学者";    //定义content属性
    
    
//getter     setter
    public String getName() {
        
return name;
    }
    
public void setName(String name) {
        
this.name = name;
    }
    
public String getContent() {
        
return content;
    }
    
public void setContent(String content) {
        
this.content = content ;
    }
    
    
public synchronized void set(String name, String content){        //由此方法统一设置信息
        this.setName(name) ;
        
try {
            
/* 此时这个地方加不加延迟没有任何关系,因为该方法已经同步
               在执行到此方法(get())时,此方法将会完整结束后才会执行
               到别的方法 ,所以一定会完整设置完信息之后才会轮到信息的读取方法
*/
            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()) ;
    }
} ;

/**
 * 定义生产者
 * 
@author Solitary
 
*/
class Producer implements Runnable {    //通过接口Runnable实现多线程
    private Info info = null ;            //保存Info引用
    
    
public Producer(Info info) {        //通过构造方法传递引用
        this.info = info ;
    }
    
    @Override
    
public void run() {
        
boolean flag = false ;                //定义标志位            
        for(int i = 0; i < 50; i++) {        //生产50次信息
            if(flag){    //如果标志位为true 将设置 中文内容
                this.info.set("小星""JAVA初学者") ;
                flag 
= false ;    //改变标志位,用于变换输入内容
                
            }
else{    //如果标志位为false 将设置英文内容        
                this.info.set("Solitary""Coder") ;
                flag 
= true ;    //改变标志位,用于变换输入内容
            }
        }
    }    
    
} ;

/**
 * 定义消费者
 * 
@author Solitary
 
*/
class Consumer implements Runnable {
    
private Info info = null ;        //用于保存Info引用,其目的是为了让消费者和生产者拥有同一个info
    
    
public Consumer(Info info){
        
this.info = info ;
    }
    
public void run() {
        
for(int i = 0; i < 50; i++) {        //消费和也从info中取50次消息
            this.info.get() ;
        }
    }
} ;

/**
 * 测试代码
 * 
@author Solitary\
 
*/
public class MultiThreadingDemo01 {
    
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() ;        //启动线程        
    }
}
示例二(运行效果)
Solitary --> Coder
Solitary 
--> Coder
Solitary 
--> Coder
Solitary 
--> Coder
Solitary 
--> Coder
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
Solitary 
--> Coder
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
小星 
--> JAVA初学者
观察运行结果发现,不匹配的现象已经解决了,因为设置name 与 content 的步骤都在一个同步方法中,所以不会导致设置不完整的情况,但是并没有达到我们想要的结果, 观察出现了连续出现重复的内容,这是为什么呢? 分析:这两个方法在不同的线程中,当一条线程中的方法设置完内容之后,另一个线程取出内容显示,取完之后当了土财主不让,继续取,生产者无法更新里边的内容,那显示出来的内容肯定就是重复的,因为多线程中,不能保证这个线程什么时候执行。怎样解决呢?

1.中间那块矩形代表信息载体Info实例,载体中没有产品的时候上面显示为绿灯,那么这时生产者能放入产品。而消费者不能取出产品。


2.当生产者放入产品之后灯变成红色,这时候生产者将不能放入产品,而轮到消费者取出产品,那么去完之后再次改变灯为绿色。这样一直反复执行下去。

那么载体上的那盏灯属于一个标志位,我们可以在代码中用boolean表示,那么这盏灯是属于信息载体Info的标志,因为设置/取出这两个方法都在Info中定义,生产者与消费者只是负责调用Info之中的方法,就让Info中的方法判断一下自身的标志位的状态判断是否生产或者取出。

实例三
package org.lx.multithreading ;

class Info        //定义信息类
{
    
private String name = "罗星" ;        //定义name属性
    private String content = "JAVA初学者" ;        //定义content属性
    private boolean flag = false ;    //设置标志位


    
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 String getName(){
        
return this.name ;
    }

    
public void setContent(String content){
        
this.content = content ;
    }

    
public String getContent(){
        
return this.content ;
    }
}

class Producer implements Runnable        //通过Runnable实现多线程
{
    
public 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("Solitary""Coder") ;
                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 MultiThreading
{
    
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中定义了方法:
public final void wait() throws InterruptedException
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。

当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。 

public final void notify()
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。

直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。  

当生产者线程调用此方法时,首先检查一下标志位,判断是否等待,此时消费者也在调用相应方法判断是否取出,如果可以取出,那么取出后改变标志位的状态,然后唤醒该对象中等待的线程,这时状态改变生产者既进行生产操作。


序言:
小弟是一个JAVA新手,这也是第一次写博客,此文纯属于学习笔记,以便日后复习,如果前辈们认为描述有错误的地方,或者觉得不清晰感觉思路混乱的地方,还请前辈们多多赐教,晚生感激不胜! 谢谢。

posted on 2011-10-29 18:26 Solitary 阅读(2295) 评论(1)  编辑  收藏

评论

# re: 生产者与消费者(多线程经典案例) 2014-11-11 21:53 李兴华

亲,你这明显是抄袭我的嘛,我是魔乐科技的李兴华;
希望你能改过自新  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 
<2011年10月>
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345

导航

统计

常用链接

留言簿(1)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜