线程操作案例——生产者与消费者(java)

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

温馨提示:如果不想看分析过程,可以直接看目录的3.完整代码

1. 基本实现

1)Info.java(信息类)

因为现在程序中生产者不断生产的是信息,而消费者取出的也是信息,所以定义一个保存信息的Info.java类。

public class Info {                 //定义信息类
    private String name="李兴华";    //信息名称,指定默认值
    private String content="JAVA讲师"; //信息内容,指定默认值

    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;
    }
}

2)Producer.java(生产者)

生产者和消费者现在要操作同一空间的内容,属于两个不同的线程,我们均采用实现接口Runnable来设置线程。

public class Producer implements Runnable{
    private Info info;      //保存Info引用,生产信息
    public Producer(Info info){  //通过构造方法设置info内容
        this.info=info;
    }
    @Override
    public void run() {      //覆写run()方法,来生产信息(这里设置50条交替信息)
        boolean flag=true;    //通过flag实现两组信息的交替设置
        for(int i=0;i<50;i++){
            if(flag){
                this.info.setName("李兴华");
                try {
                    Thread.sleep(300);    //添加睡眠,使得效果更加明显
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                this.info.setContent("JAVA讲师");
                flag=false;
            }else{
                this.info.setName("mldn");
                try {
                    Thread.sleep(300);    //添加睡眠,使得效果更加明显
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                this.info.setContent("www.mldnjava.cn");
                flag=true;
            }
        }
    }
}

3) Consumer.java(消费者)

可以直接理解成将info的内容拿出来

public class Consumer implements Runnable{
    private Info info;
    public Consumer(Info info){
        this.info=info;
    }
    @Override
    public void run() {     //覆写run()方法,取出info信息
        for(int i=0;i<50;i++){    //循环取得50次信息(与生产者生产保持一致)
            try {
                Thread.sleep(300);    //添加休眠,使得效果更加明显
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(this.info.getName()+"-->"+this.info.getContent());
        }
    }
}

4)ThreadCaseDemo01.java(主方法)

public class ThreadCaseDemo01 {
    public static void main(String args[]){
        Info i=new Info();    //实例化Info对象
        Producer pro=new Producer(i);
        Consumer con =new Consumer(i);
        new Thread(pro).start();
        new Thread(con).start();
    }
}

运行结果(部分):

2. 出现的问题

1)信息的题目与内容不匹配-->假设生产者线程刚向数据存储空间添加了信息的名称,还没有加入这信息的内容时,程序就切换到了消费者线程,消费者线程就会把这次信息的名称和上一个信息的内容匹配在一起。

2)重复存取问题-->生产者放入了若干次数据,消费者才开始取数据,或者是消费者取完一个数据后,还没有等到消费放入新的数据,又重复取已经取过的数据。

2.1 问题解决1——加入同步

为了让信息的题目与内容相匹配,我们在存取数据时,给姓名和内容添加同步。为了方便,我们直接在Info类添加自定义方法set()和get()来存取数据。

修改如下:

1)Info.java(信息类)

public class Info {                 //定义信息类
    private String name="李兴华";    //信息名称,指定默认值
    private String content="JAVA讲师"; //信息内容,指定默认值

    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.name=name;
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        this.content=content;
    }
    public synchronized void get(){
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(this.getName()+"-->"+this.getContent());
    }

}

然后生产者类和消费者类也应该进行相应修改

2)Producer.java(生产者)

public class Producer implements Runnable{
    private Info info;      //保存Info引用,生产信息
    public Producer(Info info){  //通过构造方法设置info内容
        this.info=info;
    }
    @Override
    public void run() {      //覆写run()方法,来生产信息(这里设置50条交替信息)
        boolean flag=true;    //通过flag实现两组信息的交替设置
        for(int i=0;i<50;i++){
            if(flag){
                this.info.set("李兴华","JAVA讲师");
                flag=false;
            }else{
                this.info.set("mldn","wwww.mldnjava.cn");
                flag=true;
            }
        }
    }
}

3) Consumer.java(消费者)

public class Consumer implements Runnable{
    private Info info;
    public Consumer(Info info){
        this.info=info;
    }
    @Override
    public void run() {     //覆写run()方法,取出info信息
        for(int i=0;i<50;i++){    //循环取得50次信息(与生产者生产保持一致)
           this.info.get();
        }
    }
}

结果:

从结果可以看出问题1已经解决,然后我们来解决问题2.

2.2 问题解决2——加入等待与唤醒

        如果想让生产者不重复生产,消费者不充分取走,则可以添加一个标志位flag(boolean型)。如果flag为true,表示可以生产,但是不能取走,如果此线程执行到了消费者线程,则应该等待;

如果flag为false,表示可以取走,但是不能生产,如果此线程执行到了生产者线程,则应该等待。

(也就是实现生产一个取一个)

在这里线程的等待和唤醒我们用Object类对线程的一些方法:

方法类型描述
public final void wait() throws InterruptedException普通线程等待
public final void notify()普通唤醒第一个等待的线程
public final void notifyAll()普通唤醒全部等待线程

这里只需要修改Info类即可:

public class Info {                 //定义信息类
    private String name="李兴华";    //信息名称,指定默认值
    private String content="JAVA讲师"; //信息内容,指定默认值
    private boolean flag=true;      //设置标志位
    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){   //用于生产者
        if(!flag){              //flag为false,不能生产,需要等待消费者消费
            try {
                super.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        this.name=name;        //flag为true的情况,需要生产者生产
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        this.content=content;
        flag=false;           //修改标志位
        super.notify();       //当生产者产生信息后,唤醒消费者进程
    }
    public synchronized void get(){
        if(flag){           //当flag为true,该生产者生产,消费者需等待
            try {
                super.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(this.getName()+"-->"+this.getContent());
        flag=true;
        super.notify();    //当消费者取出信息后,唤醒生产者进程
    }

}

 结果:(部分)

完美解决

3. 完整代码

1)Info.java(信息类)

public class Info {                 //定义信息类
    private String name="李兴华";    //信息名称,指定默认值
    private String content="JAVA讲师"; //信息内容,指定默认值
    private boolean flag=true;      //设置标志位
    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){   //用于生产者
        if(!flag){              //flag为false,不能生产,需要等待消费者消费
            try {
                super.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        this.name=name;        //flag为true的情况,需要生产者生产
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        this.content=content;
        flag=false;           //修改标志位
        super.notify();       //当生产者产生信息后,唤醒消费者进程
    }
    public synchronized void get(){
        if(flag){           //当flag为true,该生产者生产,消费者需等待
            try {
                super.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(this.getName()+"-->"+this.getContent());
        flag=true;
        super.notify();    //当消费者取出信息后,唤醒生产者进程
    }

}

2)Producer.java(生产者)

public class Producer implements Runnable{
    private Info info;      //保存Info引用,生产信息
    public Producer(Info info){  //通过构造方法设置info内容
        this.info=info;
    }
    @Override
    public void run() {      //覆写run()方法,来生产信息(这里设置50条交替信息)
        boolean flag=true;    //通过flag实现两组信息的交替设置
        for(int i=0;i<50;i++){
            if(flag){
                this.info.set("李兴华","JAVA讲师");
                flag=false;
            }else{
                this.info.set("mldn","wwww.mldnjava.cn");
                flag=true;
            }
        }
    }
}

3) Consumer.java(消费者)

public class Consumer implements Runnable{
    private Info info;
    public Consumer(Info info){
        this.info=info;
    }
    @Override
    public void run() {     //覆写run()方法,取出info信息
        for(int i=0;i<50;i++){    //循环取得50次信息(与生产者生产保持一致)
           this.info.get();
        }
    }
}

4.)ThreadCaseDemo01.java(主方法)

public class ThreadCaseDemo01 {
    public static void main(String args[]){
        Info i=new Info();    //实例化Info对象
        Producer pro=new Producer(i);
        Consumer con =new Consumer(i);
        new Thread(pro).start();
        new Thread(con).start();
    }
}

结果(部分):

4. 总结

 此代码只是简单实现了生产者与消费者每次先生产后消费的情况,利用同步和Object提供线程的等待与唤醒实现该功能,但是没有提供排队序列这个功能,存在一定不足。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值