Java多线程设计(四)生产者/消费者模式

在生活中,供求关系是很常见的。一个卖烧鸭的,一个买烧鸭的,卖烧鸭的要有烧鸭,买烧鸭的才能买到烧鸭。所以,买烧鸭的要等卖烧鸭的准备好烧鸭,她才有得买。这就是典型的生产者/消费者模式呀。用Java线程描述这些事情,也是可以妥妥的。

王大厨开了一间烧鸭店,每天生产100只烧鸭。张大妈今天家里摆酒请客,刚好要买100只烧鸭。王大厨做出的烧鸭都会挂在一个挂钩上,这些挂钩就像是一条队列,一只只烧鸭挂在上面,先做出来的烧鸭摆在前面卖给客人。当挂钩上没有烧鸭的时候,客人就必须等待王大厨重新做出烧鸭。

那么在Java的世界里,这件事情将会如下面代码呈现。

(1)首先是无辜的烧鸭,用下面的类Duck表示,每个烧鸭有一个num字段代表编号

/**
 * 烧鸭
 */
public class Duck {
    private int num;
    //..其它属性

    public Duck(int Num) {
        this.num = Num;
    }

    @Override
    public String toString() {
        return "[" + num+"号烧鸭]";
    }
}

(2)然后是鸭子队列(就是挂钩挂着的一个个鸭子~),用下面的类DuckQueue表示,其中用LinkedList类型(FIFO)的字段queue来“挂”烧鸭~其中有两个很重要的方法

  1. getDuck方法用来获取队列queue中的Duck对象,注意了, queue.removeFirst();会把队列的第一个Duck对象移出队列。其中有一步很重要,就是while中的判断,当队列中有对象才能获取对象,就像是有烧鸭,别人才可以买。没有怎么办?等呗,wait方法就是让当前线程等着,等别人叫他。就像是王大厨看没烧鸭了,先叫张大妈等着,等有了再告诉张大妈,让她拿走。
  2. putDuck方法用来将一个Duck对象放进队列queue中,当然了是放在队列的最后面,当放完后,就用notifyAll通知等待中的线程。就像是王大厨每做出一只烤鸭都要告诉一声张大妈,有烧鸭了~不管之前挂着有没有,反正就是要通知一声。

注意,这两个方法必须是同步方法,因为他们都在操作queue,而且waitnotifyAll方法必须在同步方法或同步代码块中执行。

import java.util.LinkedList;

/**
 * 烧鸭队列
 */
public class DuckQueue {
    // 存放Duck对象
    private LinkedList<Duck> queue = new LinkedList<Duck>();

    /**
     *获取queue中的Duck对象   
     */
    public synchronized Duck getDuck(){
        while(queue.size() <= 0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return queue.removeFirst();
    }

    /**
     *往 queue中添加Duck对象
     */
    public synchronized void putDuck(Duck duck){
        queue.addLast(duck);
        notifyAll();
    }
}

(3)然后就是生产者类,就是做烤鸭的,用下面的类Producer表示。其中random只是用来生成随机数,然后用这个设置线程睡眠时间,模拟现实中生产烧鸭所需的时间各不相同,下面的消费者类也是如此。Producer是一个线程类,在run方法中依次生产100只烧鸭并挂在队列上。

import java.util.Random;

/**
 * 生产者
 */
public class Producer extends Thread {
    private DuckQueue queue;
    private Random random;

    public Producer(String name, DuckQueue queue, Random random) {
        super(name);// 设置线程名称
        this.queue = queue;
        this.random = random;
    }

    public void run() {
        for (int i = 1; i <= 100; i++) {
            Duck duck = new Duck(i);// 产生烧鸭
            queue.putDuck(duck);// 挂上烧鸭
            System.out.println(Thread.currentThread().getName() + "生产了" + duck);
            int sleepTime = random.nextInt(100);
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

(4)消费者类,就是拿走烧鸭的,用下面的类Consumer表示。Consumer是一个线程类,在run方法中拿走100个烧鸭,不过是一次拿一个。

/**
 * 消费者
 */
public class Consumer extends Thread{

    private DuckQueue queue;
    private Random random;

    public Consumer(String name,DuckQueue queue,Random random) {
        super(name);//设置线程名称
        this.queue = queue;
        this.random = random;
    }

    public void run() {
        for (int i = 1; i <= 100; i++) {
            Duck duck = queue.getDuck();// 从队列中拿走烧鸭
            System.out.println(Thread.currentThread().getName() + "拿走了"+duck);
            int sleepTime = random.nextInt(100);
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

(5)最后是测试程序,用下面的类Main表示。其中创建Producer对象时所用的数字321654987没有什么特别的意义,只不过是用于随机数的生成,123456798亦是如此。

public class Main {

    public static void main(String[] args) {
        DuckQueue queue = new DuckQueue();
        new Producer("王大厨", queue, new Random(321654987)).start();
        new Consumer("张大妈", queue, new Random(123456798)).start();
    }
}

(6)最后的输出结果,只截取了前面一段和后面一段。

张大妈拿走了[1号烧鸭]
王大厨生产了[1号烧鸭]
王大厨生产了[2号烧鸭]
王大厨生产了[3号烧鸭]
张大妈拿走了[2号烧鸭]
王大厨生产了[4号烧鸭]
张大妈拿走了[3号烧鸭]
王大厨生产了[5号烧鸭]
张大妈拿走了[4号烧鸭]
张大妈拿走了[5号烧鸭]
王大厨生产了[6号烧鸭]
张大妈拿走了[6号烧鸭]
王大厨生产了[7号烧鸭]
张大妈拿走了[7号烧鸭]
王大厨生产了[8号烧鸭]
王大厨生产了[9号烧鸭]
....
王大厨生产了[92号烧鸭]
张大妈拿走了[91号烧鸭]
王大厨生产了[93号烧鸭]
王大厨生产了[94号烧鸭]
王大厨生产了[95号烧鸭]
张大妈拿走了[92号烧鸭]
张大妈拿走了[93号烧鸭]
王大厨生产了[96号烧鸭]
张大妈拿走了[94号烧鸭]
王大厨生产了[97号烧鸭]
张大妈拿走了[95号烧鸭]
张大妈拿走了[96号烧鸭]
王大厨生产了[98号烧鸭]
张大妈拿走了[97号烧鸭]
王大厨生产了[99号烧鸭]
张大妈拿走了[98号烧鸭]
王大厨生产了[100号烧鸭]
张大妈拿走了[99号烧鸭]
张大妈拿走了[100号烧鸭]

从上面的输出结果大家可以看出,这就是我们期待的结果王大厨生产出烧鸭,张大妈拿走,两者不会搞混,不会发生王大厨还没生产出2号烧鸭而张大妈就拿走2号烧鸭或者更后编号的烧鸭这种鬼事。

总结一下,在整个程序中,最妙的地方在于交流,两条线程就像人一样在通话。虽然通的话只有几句话。

张大妈:“诶,现在烧鸭队列里有烧鸭嘛”—> [while(queue.size() <= 0)]
张大妈:“没呀,好吧,我等着”(while中的判断为true的时候)—> [wait();]
张大妈:“有了,好吧,我拿走咯~”(while中的判断为false的时候)—>[queue.removeFirst();]

王大厨:“有烧鸭了,快来拿”—> [queue.addLast(duck);notifyAll]

其实上面的程序还可以变形一下,就是说王大厨做出一只烧鸭张大妈拿一只,如果张大妈不拿,则王大厨就不做先,知道张大妈拿走再做。如果是这样的话只需要将DuckQueue类写成下面这样子,也是“通话”的形式,只不过条件变了。

/**
 * 烧鸭队列
 */
public class DuckQueue {
    // 存放Duck对象
    private LinkedList<Duck> queue = new LinkedList<Duck>();

    /**
     *获取queue中的Duck对象   
     */
    public synchronized Duck getDuck(){
        while(queue.size() != 1){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
         Duck duck = queue.removeFirst();
         notifyAll();
         return duck;
    }

    /**
     *往 queue中添加Duck对象
     */
    public synchronized void putDuck(Duck duck){
        while(queue.size() != 0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        queue.addLast(duck);
        notifyAll();
    }
}

好了,这就是生产者/消费者模式,当然其中还有很多不同的实现方法,有些方法更优。不过这种模式的思想就是这样子,你生产来我消费,你没东西来我等待,你有东西来记得叫我。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值