生产者消费者问题是一个经典的同步问题,曾经在操作系统中接触过,还有哲学家就餐问题.
今天在学习多线程时,又遇到这个例子,今天就讨论一下生产者消费者问题的解决方法.
1.一对一
代码实现
public class Resouce {
private String name;
//计数器
private int count = 1;
private Boolean flag = false;
public synchronized void set() {
if(flag)
try {
wait();
}catch(Exception e) {
}
this.name = "+商品+"+count++;
System.out.println(Thread.currentThread().getName()+
"生产者"+"...."+name);
//置标志为真
flag=true;
//唤醒消费者进程
this.notify();
}
public synchronized void out() {
if(!flag)
try {//flag为假,等待
wait();
}catch(Exception e) {}
//打印消费者进程
System.out.println(Thread.currentThread().getName()+
"消费者"+"......."+name);
//置标志为假
flag=false;
//唤醒生产者进程
this.notify();
}
}
public class Producer implements Runnable {
//生产者
private Resouce r;
public Producer(Resouce r) {
this.r = r;
}
public void run() {
while(true) {
r.set();
}
}
}
public class Consumer implements Runnable {
//消费者
private Resouce res;
public Consumer(Resouce res) {
this.res= res;
}
public void run() {
while(true) {
res.out();
}
}
}
public class ProducerAndConsumer {
//主线程
public static void main(String[] args) {
//资源
Resouce res = new Resouce();
//创建生产者消费者对象
Producer p = new Producer(res);
Consumer con = new Consumer(res);
//为对象创建线程
Thread t1 = new Thread(p);
Thread t2 = new Thread(con);
//开启线程
t1.start();
t2.start();
}
}
eclipse中运行太快,用cmd运行结果
从图片中可以看出,生产者生产一个商品,消费者就消费一个,而且往下执行,也没有发现错误,说明这个一对一解决了.
2.多对多
接下来,看多对多,为简单起见,再创建一个消费者进程和生产者进程,在主函数中创建即可
观察运行结果
很给力,刚开始运行就出现了错误,商品2被消费了好多次,商品3没有被消费
这是为什么呢?
跟着代码走一遍
**假设第一个生产者线程先进来判断if(flag),不成立,往下执行,生产完产品后,置flag为true,此时线程池中没有线程等待,第一个生产者线程继续执行set函数,if(flag成立),wait()
此时,t1失去资格
**这时第二个生产者线程进来,在同样的地点失去资格
**然后消费者线程t3登场,flag=true,执行消费函数后,置flag为false,并唤醒t1,因为flag为false,t3wait()
**同样,第二个消费者线程t4进场,在同样的地点wait(),flag=false
**这时t1是醒着的,继续往下执行,置flag为true,并唤醒线程池中第一个进程,往前看,可以发现,第一个进程是t2,唤醒t2,然后t1继续执行,在同样的地点wait()
**t2获得CPU执行权,因为t2在wait之前就已经判断过if(flag),从wait的地方继续往后执行.
即使这时候false=true,理论上t2不应该执行.这就导致了t1生产的商品没有被消费
继续按上述逻辑,不难退出一个商品被消费多次的情况.
这种情况的出现,是由于if(flag)只是判断一次,在if下wait的线程获得执行权,就不用再次判断flag状态了,显然是不安全的,那么怎么使程序多次判断flag状态呢?
很简单,用while就可以
用while之后的执行结果
程序好像出问题了呢,是死锁吗,什么原因?
让我们回到分析的最后一步.在代码中,我们使用的是notify()函数,每次只唤醒线程池中的第一个线程.所以最后一步中,t2是被t1唤醒的,此时的flag=true,t2–>wait(),但是他没有唤醒任何一个进程,也就是说线程池中的生产者和消费者进程都在wait(),并不是死锁.
怎么解决?
将notify()改为notifyAll()就可以,notifyAll()函数是唤醒线程池中所有等待的线程.
最终代码
class Resouce {
private String name;
//计数器
private int count = 1;
private Boolean flag = false;
public synchronized void set() {
while(flag)
try {
wait();
}catch(Exception e) {
}
this.name = "+商品+"+count++;
System.out.println(Thread.currentThread().getName()+
"生产者"+"...."+name);
//置标志为真
flag=true;
//唤醒消费者进程
this.notifyAll();
}
public synchronized void out() {
while(!flag)
try {//flag为假,等待
wait();
}catch(Exception e) {}
//打印消费者进程
System.out.println(Thread.currentThread().getName()+
"消费者"+"......."+name);
//置标志为假
flag=false;
//唤醒生产者进程
this.notifyAll();
}
}
class Producer implements Runnable {
//生产者
private Resouce r;
public Producer(Resouce r) {
this.r = r;
}
public void run() {
while(true) {
r.set();
}
}
}
class Consumer implements Runnable {
//消费者
private Resouce res;
public Consumer(Resouce res) {
this.res= res;
}
public void run() {
while(true) {
res.out();
}
}
}
public class test {
//主线程
public static void main(String[] args) {
//资源
Resouce res = new Resouce();
//创建生产者消费者对象
Producer p = new Producer(res);
Consumer con = new Consumer(res);
//为对象创建线程
Thread t1 = new Thread(p);
Thread t2 = new Thread(con);
Thread t3 = new Thread(p);
Thread t4 = new Thread(con);
//开启线程
t1.start();
t3.start();
t2.start();
t4.start();
}
}
运行结果
jdk升级至1.5之后的解决方法
在jdk1.5之后,提供了多线程的升级结局方案,将同步的Synchronized替换成显式的Lock操作
将object类中的wait,notify,notifyAll,替换成了Condition对象,该对象可以通过lock对象获取
接口类Lock,Condition ,方法:await(),signal(),signalAll()对应的等待和唤醒操作
Lock接口中,有lock()和unlock()方法,前者是获取锁,后者释放锁.
在本问题中,通过彼此唤醒来实现消费者生产者的同步问题的解决
实现代码
import java.util.concurrent.locks.*;
class Resouce {
private String name;
//计数器
private int count = 1;
private Lock lock = new ReentrantLock();
private Boolean flag = false;
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();
public void set() throws InterruptedException {
lock.lock();
try {
while(flag)
condition_pro.await();
this.name = "+商品+"+count++;
System.out.println(Thread.currentThread().getName()+"生产者"+"...."+name);
//置标志为真
flag=true;
//唤醒消费者进程
condition_con.signal();
}finally {
//解除锁
lock.unlock();
}
}
public void out() throws InterruptedException {
//获取锁
lock.lock();
try {
while(!flag)
condition_con.await();
//打印消费者进程
System.out.println(Thread.currentThread().getName()+"消费者"+"......."+name);
//置标志为假
flag=false;
//唤醒生产者进程
condition_pro.signal();
}finally {
//解除锁
lock.unlock();
}
}
}
class Producer implements Runnable {
//生产者
private Resouce r;
public Producer(Resouce r) {
this.r = r;
}
public void run() {
while(true) {
try {
r.set();
} catch (InterruptedException e) {}
}
}
}
class Consumer implements Runnable {
//消费者
private Resouce res;
public Consumer(Resouce res) {
this.res= res;
}
public void run() {
while(true) {
try {
res.out();
} catch (InterruptedException e) {}
}
}
}
public class ProdecerAndConsumer2 {
//主线程
public static void main(String[] args) {
//资源
Resouce res = new Resouce();
//创建生产者消费者对象
Producer p = new Producer(res);
Consumer con = new Consumer(res);
//为对象创建线程
Thread t1 = new Thread(p);
Thread t2 = new Thread(con);
Thread t3 = new Thread(p);
Thread t4 = new Thread(con);
//开启线程
t1.start();
t3.start();
t2.start();
t4.start();
}
}