Java 多线程 生产者消费者问题

Java 多线程 生产者消费者问题

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

生产者的主要作用是生成一定量的数据资源,然后重复此过程,消费者不断消耗这些数据。两者间需要通过线程间的通信来共同操作数据。

生产者消费者的代码实现

思路

  1. 描述处理的资源。
  2. 描述生产者,具备着生产的任务。
  3. 描述消费者,具备着消费的任务。
//描述资源。属性:商品名称和编号,行为:对商品名称赋值,获取商品。
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) {
            }
        }
        // 给成员变量赋值并加上编号。
        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(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();
        }
    }
}

class ThreadDemo {
    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!");
    }
}

等待/唤醒机制:

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

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

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

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

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

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

class ThreadDemo 
{
	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判断后,死锁了。

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

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

//描述资源。属性:商品名称和编号,  行为:对商品名称赋值,获取商品。
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();
	}
}

Lock锁解决多生产多消费问题

lock锁实现,使用condition做线程之间的同步。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class BoundedBuffer {
    //锁
    final Lock lock = new ReentrantLock();
    //监视器
    //非满
    final Condition notFull = lock.newCondition();
    //非空
    final Condition notEmpty = lock.newCondition();

    //数组容器,存的是对象
    final Object[] items = new Object[100];
    //用来操作数组的变量:存,取,计数器
    //有数组就得有指针,存和取的指针得分别定义putptr、takeptr,同时还得记录数组中元素的个数
    int putptr, takeptr, count;

    //await()方法抛出异常,因为方法内没有做catch处理,想让调用者处理。也可以在方法内处理
    public void put(Object x) throws InterruptedException {
        //获取锁。此时,take()不能取,因为用的是同一个锁,互斥
        lock.lock();
        try {
            //判断标记时一定要用while。因为每次醒来都先判断标记,安全
            //while是必须的
            while (count == items.length) {
                //存满了,生产者等待
                notFull.await();
            }
            //生产一个存一个
            items[putptr] = x;
            if (++putptr == items.length) {
                //生产到最后一个后,继续从0开始存
                putptr = 0;
            }
            ++count;
            System.out.println("生产:" + x);
            //signal()、signalAll()用哪个不一定。如果signal()能实现唤醒对方,就不需要signalAll()
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            //判断标记时一定要用while。因为每次醒来都先判断标记,安全
            //while是必须的
            while (count == 0) {
                //取完了,消费者等待
                notEmpty.await();
            }
            //从默认的0角标开始取
            Object x = items[takeptr];
            if (++takeptr == items.length) {
                //取到最后一个后,继续从0开始取
                takeptr = 0;
            }
            --count;
            //signal()、signalAll()用哪个不一定。如果signal()能实现唤醒对方,就不需要signalAll()
            notFull.signal();
            return "消费:" + x;
        } finally {
            lock.unlock();
        }
    }
}

//描述生产者生产的商品
class Goods {
    private String name;
    private String id;

    Goods(String name, String id) {
        this.name = name;
        this.id = id;
    }

    public String toString() {
        return "Goods=[name=" + name + ",id=" + id + "]";
    }
}

//描述生产者
class Producer implements Runnable {
    private BoundedBuffer r;
    //描述程序的商品编号
    private int count = 1;

    //生产者以创建就应该明确资源
    Producer(BoundedBuffer r) {
        this.r = r;
    }

    //生产者生产商品的任务
    public void run() {
        //生产者无限制的生产
        while (true) {
            try {
                r.put(new Goods("商品", count + ""));
                count++;
            } catch (InterruptedException e) {
            }

        }
    }
}

//描述消费者
class Consumer implements Runnable {
    private BoundedBuffer r;

    //生产者以创建就应该明确资源
    Consumer(BoundedBuffer r) {
        this.r = r;
    }

    //生产者生产商品的任务
    public void run() {
        while (true) {
            try {
                System.out.println(r.take());
            } catch (InterruptedException e) {
            }
        }
    }
}

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

猜你喜欢

转载自blog.csdn.net/m0_49297152/article/details/107958804