多线程【生产者消费者模型】

多线程【生产者消费者模型】

生产者消费者问题

在生活中经常会遇到两方都在处理某一资源,而处理的方式不同。比如:水池中注水和排水,煤场中往进运煤和往出拉煤。这些操作处理的资源都相同,只是他们操作的方式有所不同。这类操作就多线程中另外一种高级应用,即多生产多消费

生产者消费者代码

多线程中最为常见的应用案例:生产者消费者问题。

举例:生产者在生产商品,而消费者在消费生产的商品。生产者把生产的商品放进容器中,而消费者从容器中取出商品进行消费。可是在整个过程中,如果容器装满了,那么生产者应该停止生产,如果容器中没有商品了,消费应该停止消费。这就是一个典型的多生产,多消费的案例。

在学习过程中,为了代码简单明了,大家很容易看懂,就把上述的多生产和多消费进行简化,要求生产者生产一个商品,消费者消费一个商品,然后生产者继续生产,消费者进行消费,以此类推下去。

分析案例

生产消费同时执行,需要多线程。但是执行的任务却不相同,处理的资源确实相同的:线程间的通信

思路

  1. 描述处理的资源。
  2. 描述生产者,具备着生产的任务。
  3. 描述消费者,具备着消费的任务。
public class ThreadDemo1 {
    public static void main(String[] args) {
        // 创建资源对象
        Resource r = new Resource();
        // 创建生产者对象
        Producer pro = new Producer(r);
        // 创建消费者对象
        Consumer con = new Consumer(r);

        // 创建线程对象
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(con);
        // 开启线程
        t1.start();
        t2.start();
        System.out.println("Hello World!");
    }
}
// 描述资源。属性:商品名称和编号,行为:对商品名称赋值,获取商品。
class Resource {
    private String name;// 商品名称
    private int count = 1;// 编号
    // 对外提供设置商品的方法
    public void set(String name) {
        // 给成员变量赋值并加上编号。
        this.name = name + count;
        // 编号自增。
        count++;
        // 打印生产了哪个商品。
        System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
    }
    // 对外消费商品的方法
    public void get() {
        System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
    }
}
//描述生产者
class Producer implements Runnable {
    // 资源属性
    private Resource r;
    // 生产者一创建就应该明确资源
    Producer(Resource r) {
        this.r = r;
    }
    // 生产者生产商品的任务
    public void run() {
        // 生产者无限制的生产
        while(true) {
            r.set("面包");
        }
    }
}
//描述消费者
class Consumer implements Runnable {
    // 资源属性
    private Resource r;
    //生产者以创建就应该明确资源
    Consumer(Resource r) {
        this.r = r;
    }
    //生产者生产商品的任务
    public void run() {
        while(true) {
            r.get();
        }
    }
}

生产者消费者问题的发生

上述代码进行运行时发现有严重的问题。

问题描述

数据错误:已经被生产很早期的商品,才被消费到。

​ 出现线程安全问题,加入了同步解决。使用同步函数。

// 描述资源。属性:商品名称和编号,行为:对商品名称赋值,获取商品。
class Resource {
    private String name;// 商品名称
    private int count = 1;// 编号
    // 对外提供设置商品的方法
    public synchronized void set(String name) {
        // 给成员变量赋值并加上编号。
        this.name = name + count;
        // 编号自增。
        count++;
        // 打印生产了哪个商品。
        System.out.println(Thread.currentThread().getName() + ".....生产者...." + this.name);
    }
    // 对外消费商品的方法
    public synchronized void get() {
        System.out.println(Thread.currentThread().getName() + ".....消费者...." + this.name);
    }
}

加入同步后又有新的问题产生了。

问题描述

流程错误:发现了连续生产却没有消费,同时对同一个商品进行多次消费。

当容器中没有面包时,就生产,如果有了面包,就不要生产。

当容器中已有面包时,就消费,如果没有面包,就不要消费。

等待唤醒机制

生产者生产了商品后应该告诉消费者来消费。这时的生产者应该处于等待状态。消费者消费了商品后,应该告诉生产者,这时消费者处于等待状态。

等待:wait();

通知:notify();

public class ThreadDemo2 {
    public static void main(String[] args) {
        // 创建资源对象
        Resource r = new Resource();
        // 创建生产者对象
        Producer pro = new Producer(r);
        // 创建消费者对象
        Consumer con = new Consumer(r);
        // 创建线程对象
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(con);
        // 开启线程
        t1.start();
        t2.start();
        System.out.println("Hello World!");
    }
}
//描述资源。属性:商品名称和编号,行为:对商品名称赋值,获取商品。
class Resource {
    private String name;// 商品名称
    private int count = 1;// 编号
    private boolean flag = false;

    // 对外提供设置商品的方法
    public synchronized void set(String name) {
        if (flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println(e);
            }
        }
        // 给成员变量赋值并加上编号。
        this.name = name + count;
        // 编号自增。
        count++;
        // 打印生产了哪个商品。
        System.out.println(Thread.currentThread().getName() + ".....生产者...." + this.name);
        // 将标记改为true。
        flag = true;
        // 唤醒消费者。
        this.notify();
    }

    // 对外提供获取商品
    public synchronized void get() {
        if (!flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println(e);
            }
        }
        System.out.println(Thread.currentThread().getName() + ".....消费者...." + this.name);
        // 将标记改为false。
        flag = false;
        // 唤醒生产者。
        this.notify();
    }
}

// 描述生产者
class Producer implements Runnable {
    private Resource r;
    // 生产者以创建就应该明确资源
    Producer(Resource r) {
        this.r = r;
    }
    // 生产者生产商品的任务
    public void run() {
        // 生产者无限制的生产
        while (true) {
            r.set("面包");
        }
    }
}
// 描述消费者
class Consumer implements Runnable {
    private Resource r;
    // 生产者以创建就应该明确资源
    Consumer(Resource r) {
        this.r = r;
    }
    // 生产者生产商品的任务
    public void run() {
        while (true) {
            r.get();
        }
    }
}

等待/唤醒机制:

wait(): 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。

notify():会唤醒线程池中任意一个等待的线程。

notifyAll():会唤醒线程池中所有的等待线程。

记住:这些方法必须使用在同步中,因为必须要标识wait,notify等方法所属的锁。同一个锁上的notify,只能唤醒该锁上的被wait的线程。

为什么这些方法定义在Object类中

因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然时Object类中的方法。

多生产多消费问题以及解决方案

上述程序只是一个生产和一个消费者,其实就是所谓的单生产和单消费,可是我们都知道生活中经常会有多个生产者和消费者,把代码改为多个生产者或多个消费者。

class ThreadDemo3 
{
	public static void main(String[] args) 
	{
		//创建资源对象
		Resource r = new Resource();
		//创建生产者对象
		Producer pro = new Producer(r);
		//创建消费者对象
		Consumer con = new Consumer(r);
		//创建线程对象
		Thread t1 = new Thread(pro);// 线程1生产
		Thread t2 = new Thread(pro);// 线程2生产
		Thread t3 = new Thread(con);// 线程3消费
		Thread t4 = new Thread(con);// 线程4消费
		//开启线程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

把生产者和消费者改为多个时,又有新的问题发生了。

问题描述

流程错误:生产了商品没有被消费,同一个商品被消费多次。

问题原因: 只要让被唤醒的线程必须判断标记就可以了。将if判断标记的方式改为while循环标记。记住:多生产多消费,必须是while循环条件。

解决:只要让被唤醒的线程必须判断标记就可以了。将if判断标记的方式改为while循环标记。记住:多生产多消费,必须时while循环条件。

//描述资源。属性:商品名称和编号,  行为:对商品名称赋值,获取商品。
class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;
	//对外提供设置商品的方法
	public synchronized void set(String name)
	{
		while(flag)
		{
			try{wait();}catch(InterruptedException e){}
		}
		//给成员变量赋值并加上编号。
		this.name = name + count;
		//编号自增。
		count++;
		//打印生产了哪个商品。
		System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
		//将标记改为true。
		flag = true;
		//唤醒消费者。
		this.notify();
	}
	public synchronized void get()
	{
		while(!flag)
		{
			try{wait();}catch(InterruptedException e){}
		}
		System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
		//将标记改为false。
		flag = false;
		//唤醒生产者。
		this.notify();
	}
}

当把if改为while之后又出现问题了。

问题描述

流程错误:发现while判断后,死锁了。

原因:本方唤醒了本方生产方,唤醒了线程池中生产方的线程。

解决:希望本方要唤醒对方,没有对应的方法,所以只能唤醒所有。

整合所有的完整代码:

public class ThreadDemo4 {
    public static void main(String[] args) {
        //创建资源对象
        Resource r = new Resource();
        //创建生产者对象
        Producer pro = new Producer(r);
        //创建消费者对象
        Consumer con = new Consumer(r);
        //创建线程对象
        Thread t1 = new Thread(pro);// 线程1生产
        Thread t2 = new Thread(pro);// 线程2生产
        Thread t3 = new Thread(con);// 线程3消费
        Thread t4 = new Thread(con);// 线程4消费
        //开启线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
// 描述生产者
class Producer implements Runnable {
    private Resource r;
    // 生产者以创建就应该明确资源
    Producer(Resource r) {
        this.r = r;
    }
    // 生产者生产商品的任务
    public void run() {
        // 生产者无限制的生产
        while (true) {
            r.set("面包");
        }
    }
}
// 描述消费者
class Consumer implements Runnable {
    private Resource r;
    // 生产者以创建就应该明确资源
    Consumer(Resource r) {
        this.r = r;
    }
    // 生产者生产商品的任务
    public void run() {
        while (true) {
            r.get();
        }
    }
}
//描述资源。属性:商品名称和编号,  行为:对商品名称赋值,获取商品。
class Resource {
    private String name;
    private int count = 1;
    private boolean flag = false;
    //对外提供设置商品的方法
    public synchronized void set(String name) {
        while(flag) {
            try{wait();}catch(InterruptedException e){}
        }
        //给成员变量赋值并加上编号。
        this.name = name + count;
        //编号自增。
        count++;
        //打印生产了哪个商品。
        System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
        //将标记改为true。
        flag = true;
        //唤醒消费者。
        this.notifyAll();
    }
    public synchronized void get() {
        while(!flag) {
            try{wait();}catch(InterruptedException e){}
        }
        System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
        //将标记改为false。
        flag = false;
        //唤醒生产者。
        this.notifyAll();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值