生产者消费者模型是多线程当中比较经典的一个模型,该模型模拟线程间公用同一个对象,通过调度不同的线程休眠、等待和唤醒起到预防死锁的作用。
首先列举一个线程死锁的例子,下面这个例子是模拟服务生和顾客争执先服务和先收钱而产生的死锁问题,代码如下:
package deadlock;
/**
* 以顾客和服务生对峙举例死锁
* Created by zhuxinquan on 16-1-26.
*/
public class DeadLockDemo {
public static void main(String[] args) {
new DeadThread();
}
}
//顾客
class Customer {
public synchronized void say(Waiter w){
System.out.println("先服务");
w.doService();
}
public synchronized void doService(){
System.out.println("同意先收钱");
}
}
//服务生
class Waiter {
public synchronized void say(Customer c){
System.out.println("先收钱");
c.doService();
}
public synchronized void doService(){
System.out.println("同意先服务");
}
}
class DeadThread implements Runnable{
Customer c = new Customer();
Waiter w = new Waiter();
//在构造方法中创建线程并启动,两个同步方法相互等待对方执行完毕产生死锁
public DeadThread(){
new Thread(this).start();
w.say(c);
}
@Override
public void run() {
c.say(w);
}
}
首先分析一下整个过程,main方法开始执行new一个DeadThread类,该类有两个成员变量c和w,分别代表Customer和Waiter,并且DeadThread类继承Runnable接口,当new一个DeadThread类的时候,调用空构造方法,在构造方法中创建一个线程并启动,创建的线程负责执行c.say(w),线程创建完毕就会执行w.say(c),如果忽略创建线程消耗的时间,就可以简单的认为c.say(w)方法和w.say(c)方法是同时在两个线程当中执行的,为了后续区分,我们默认线程1当中执行的是c.say(w),在线程2当中执行的是w.say(c),然后回到两个线程方法执行的部分。
两个线程中的方法都是同步方法,因而在执行过程中会占有this对象对于线程1来说就会占有占有c对象,对于线程2来说就回占有w对象,所以在两个线程执行完输出后就需要等待对方所占有的对象,如果同时开始等待,即就是在线程1中的c对象执行完成输出后等待线程2的w对象,而线程2的w对象在执行完成输出之后等待线程1的c对象,两个线程相互等待就回产生死锁。
=========
值得注意的是,死锁并不是每次都会发生,只有当两个线程同时相互等待对方所占有的资源并且都不愿释放自己的资源时才会发生。如果上面的线程2在执行之前线程1已经执行完毕不会发生死锁了。
=========
下面使用厨师和服务员公用食物对象为例,厨师作为生产者生产食物(食物数量增加),而服务员作为消费者为顾客提供食物(食物数量减少),他们共享食物对象,当食物数量为0时消费者线程阻塞,当食物数量为1时生产者线程阻塞。
首先贴上代码:
package ConsumerAndProducterExample;
/**
* Created by zhuxinquan on 16-1-27.
*/
public class ConsumerAndProducterExample {
public static void main(String[] args) {
Food f = new Food();
Producter p = new Producter(f);
Consumer c = new Consumer(f);
new Thread(p).start();
new Thread(c).start();
}
}
//生产者:厨师
class Producter implements Runnable{
private Food food;
public Producter(Food food){
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2 == 0){
food.set("炒鸡蛋", "韭菜炒鸡蛋");
}else{
food.set("炒米饭", "韭菜鸡蛋炒米饭");
}
}
}
}
//消费者:服务员
class Consumer implements Runnable{
private Food food;
public Consumer(Food food){
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
food.get();
}
}
}
//产品:食物
class Food{
private String name;
private String desc;
private boolean flag = true; //true表示可以生产
//false表示可以消费
//生产产品
public synchronized void set(String name, String desc){
if(!flag){
try {
this.wait(); //线程处于等待状态,等待唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setName(name);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setDesc(desc);
flag = false; //表示可以进行消费
this.notify();
}
//消费产品
public synchronized void get(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name + ":" + this.desc);
flag = true; //表示可以生产
this.notify();
}
public Food(String name, String desc) {
this.name = name;
this.desc = desc;
}
public Food() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
食物对象中的set方法和get方法都是同步方法,并且在创建线程时,生产者和消费者使用的是同一个food对象,当两个线程同时开始之后由于food对象初始化flag为true表示可以生产不能消费,因而生产者线程继续执行,而消费者线程执行this.wait()等待其它线程唤醒,当生产者线程执行完this.setName()和this.setDesc()方法之后,改变flag表示可以消费,然后唤醒所有的进程,此处就会将消费者进程重新唤醒,然后消费者线程和生产者线程都会继续执行,同样首先判断flag标志,消费者线程可以执行,生产者线程由于flag为false,所以线程阻塞等待其他线程唤醒。同样当消费者线程在执行完成get方法前也会将标志flag改为true,并且唤醒所有的进程。此后两个线程就会循环的执行20次get和set方法,直至两个线程都结束。
+++++++++++++++++++++++++++++++++++
!!!!如有错误之处,还望高手指正!!!