Java 多线程 生产者消费者问题
在生活中经常会遇到两方都在处理同一资源,而处理的方式不同。比如:水池中注水和排水,煤场中往进运煤和往出拉煤。这些操作处理的资源都相同,只是他们操作的方式有所不同。这类操作就多线程中另外一种高级应用,即多生产和多消费。
生产者的主要作用是生成一定量的数据资源,然后重复此过程,消费者不断消耗这些数据。两者间需要通过线程间的通信来共同操作数据。
生产者消费者的代码实现
思路:
- 描述处理的资源。
- 描述生产者,具备着生产的任务。
- 描述消费者,具备着消费的任务。
//描述资源。属性:商品名称和编号,行为:对商品名称赋值,获取商品。
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();
}
}