阻塞队列与生产者消费者模式

最近在学习《JAVA程序员修炼之道》的时候,顺带重读了JAVA之母的那本《JAVA并发编程实战》。在此,对concurrent包中的BlockingQueue的几种常见用法做一个小结,以备用时查阅。首先,我们回顾一下阻塞队列和它的常见用途——生产者消费者模式的基本知识。


什么是生产者消费者模式

生产者-消费者模式是一种将“找出需要完成的工作”与“执行工作”两个过程分开的设计模式,生产者负责把准备好的工作项放入一个“待完成”列表以备后续处理,而消费者负责从该列表中取出工作项并执行工作。它的好处在于,生产者与消费者彼此不关心对方的状态,数量甚至实现方式,从而将两者的代码解耦;另一方面,该模式简化了负载管理,使得开发者更易于处理生产者与消费者之间速率的差异。


什么是阻塞队列

阻塞队列,顾名思义,首先它是一个队列,可以使得数据由队列的一端输入,从另外一端输出。如下图所示:


而阻塞队列的不同之处在于,提供了阻塞的入队方法和阻塞的出队方法。即,如果队列满了,则入队方法将一直阻塞直到有空间可用;如果队列空了,则出队方法一直阻塞到有元素可用。如下图所示:

                            

从上图不难发现,阻塞队列是生产者-消费者模式的天生支持者,利用这座桥梁,可以很轻松的在程序中实现这个模式。


复习完上述基本概念以后,我们开始进入今天的正题——JDK1.5引入的阻塞队列java.util.concurrent.BlockingQueue


BlockingQueue

BlockingQueue是阻塞队列的JAVA版,它提供了可阻塞的put和take方法,以及可定时(也可立即中止)的offer和poll方法。 这里要注意一点,队列存在有界队列与无界队列之分,无界队列永远不会填满,所以无界队列上的put方法永远不会阻塞,在实际应用中要考虑内存占用的问题。其接口如下所示:

add        增加一个元索                     如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove   移除并返回队列头部的元素    如果队列为空,则抛出一个NoSuchElementException异常
element  返回队列头部的元素             如果队列为空,则抛出一个NoSuchElementException异常
offer       添加一个元素并返回true       如果队列已满,则返回false
poll         移除并返问队列头部的元素    如果队列为空,则返回null
peek       返回队列头部的元素             如果队列为空,则返回null
put         添加一个元素                      如果队列满,则阻塞
take        移除并返回队列头部的元素     如果队列为空,则阻塞


BlockingQueue与concurrent包中的很多组件(如ScheduledExecutorService,CompletionService等)可以配合使用,起到事半功倍的效果。同时concurrent包的很多组件源码中也依赖于BlockingQueue,例如我们熟知的一系列线程池框架接口Executor的实现者们。

BlockingQueue的常见用法


这里,我总结了BlockingQueue的几种典型用法与用途:

1、经典型
/**
 * <p/>
 * <br>==========================
 * <br> 开发:sunli
 * <br> 版本:1.0
 * <br> 创建时间:2013-11-16
 * <br>==========================
 */
public class TestBlockingQueue {

    /**
     * 场景:小镇的火车站里源源不断的走出乘客,出站口有一个出租车等候点,乘客们在这里排队等候出租车。
     * 小镇上一共有3辆出租车轮流的接送乘客,每辆出租车接到一位乘客,就需要花费一段时间将乘客送到目的地,并在此返回继续接其他乘客
     *
     * 1、每个人都需要排队等候,新来的排队者加入到队伍的末尾
     * 2、火车站是生产者,不断生产出新的乘客
     * 3、出租车是消费者,不断消费乘客,但每辆出租车在同一时段只能消费一个人
     */

    public static void main(String[] args) throws InterruptedException {

        ExecutorService service = Executors.newCachedThreadPool();

        //初始化一个排队队列,能够容纳10个人
        BlockingQueue<Person> queue = new ArrayBlockingQueue<Person>(10);
        Station station = new Station(queue);
        Texi texi1 = new Texi(queue,"texi1");
        Texi texi2 = new Texi(queue,"texi2");
        Texi texi3 = new Texi(queue,"texi3");

        service.execute(station);
        service.execute(texi1);
        service.execute(texi2);
        service.execute(texi3);

        Thread.sleep(3000);
        service.shutdownNow();

    }

    //定义排队的人
    static class Person {

        private String name;

        public Person(String name) {
            this.name = name;
            System.out.println(name + "被创建");
        }

        //上车出发
        public void setOff() {
            System.out.println("Nice,I will set off,I am" + name);
        }

        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    //定义生产者,假定只有一个生产者
    static class Station implements Runnable {

        private BlockingQueue<Person> queue;
        volatile static int count = 0;

        public Station(BlockingQueue<Person> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                while (true) {

                    //生产人
                    System.out.println("火车站准备生产人: " + System.currentTimeMillis());
                    Person person = new Person("SB" + count++ + "号");
                    queue.put(person);
                    System.out.println("火车站生产人完毕: " + System.currentTimeMillis());

                    //休眠300ms
                    Thread.sleep(300);
                }
            } catch (InterruptedException ex) {
            }
        }
    }

    //定义一个消费者
    static class Texi implements Runnable {

        private BlockingQueue<Person> queue;
        private String name;

        public Texi(BlockingQueue<Person> queue, String s) {
            this.name = s;
            this.queue = queue;
        }

        public void run() {

            try {
                while (true) {

                    //消费乘客
                    System.out.println("消费者" + name + "准备消费乘客: " + System.currentTimeMillis());

                    Person person = queue.take();
                    //Yahoo,出发喽!!
                    person.setOff();

                    System.out.println("消费者" + name + "消费乘客完毕: " + "|||||||" + System.currentTimeMillis());

                    //休眠1000ms,才能接下一个客
                    Thread.sleep(1000);
                }

            } catch (InterruptedException ex) {
            }
        }
    }

}
上述代码中,有一个火车站作为生产者,三个出租车作为消费者。火车站每生产一个乘客需要300ms休息才能生产下一个乘客,出租车每消费一个乘客需要1000ms休息才能消费下一个乘客。这是使用BlockingQueue的最简单直接的方式,但是如果借用concurrent包的其它组件,我们可以更轻易而高效的使用它。

2、与ScheduledExecutorService结合
/**
 * <p/>
 * <br>==========================
 * <br> 开发:sunli
 * <br> 版本:1.0
 * <br> 创建时间:2013-11-16
 * <br>==========================
 */
public class TestBlockingQueue1 {

    /**
     * 场景:出站口的另一端,还有一个公交站,因为小镇经济水平较高,来坐公交的人并不多。公交会
     * 隔较长时间来一次公交站,并接走这里等候的全部乘客,在此之前,乘客们需要在公交站排队
     * <p/>
     * 1、每个人都需要排队等候,新来的排队者加入到队伍的末尾
     * 2、火车站是生产者,不断生产出新的乘客
     * 3、公交车是消费者,不断消费乘客,但每辆公交车在同一时段消费多个人
     */

    public static void main(String[] args) throws InterruptedException {

        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);

        //初始化一个排队队列,能够容纳10个人
        BlockingQueue<Person> queue = new ArrayBlockingQueue<Person>(10);
        Station station = new Station(queue);
        Bus bus = new Bus(queue,"公交1号");
        Bus bus2 = new Bus(queue,"公交2号");

        service.execute(station);
        service.scheduleAtFixedRate(bus, 5, 10, TimeUnit.SECONDS);
        service.scheduleAtFixedRate(bus2, 3, 10, TimeUnit.SECONDS);

        Thread.sleep(30000);
        service.shutdownNow();

    }

    //定义排队的人
    static class Person {

        private String name;

        public Person(String name) {
            this.name = name;
            System.out.println(name + "被创建");
        }

        //上车出发
        public void getOn() {
            System.out.println("我上车了,哈哈,我是" + name);
        }

        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    //定义生产者,假定只有一个生产者
    static class Station implements Runnable {

        private BlockingQueue<Person> queue;
        volatile static int count = 0;

        public Station(BlockingQueue<Person> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                while (true) {

                    //生产人
                    System.out.println("火车站准备生产人: " + System.currentTimeMillis());
                    Person person = new Person("SB" + count++ + "号");
                    queue.put(person);
                    System.out.println("火车站生产人完毕: " + System.currentTimeMillis());

                    //休眠300ms
                    Thread.sleep(300);
                }
            } catch (InterruptedException ex) {
            }
        }
    }

    //定义一个消费者
    static class Bus implements Runnable {

        private final String name;
        private BlockingQueue<Person> queue;

        public Bus(BlockingQueue<Person> queue,String name) {
            this.queue = queue;
            this.name=name;
        }

        public void run() {

            try {
                System.out.println("公交车"+name+"来了");
                while (!queue.isEmpty()) {

                    //消费乘客
                    System.out.println("公交车"+name+"准备上乘客");
                    Person person = queue.poll();
                    if (person != null) {
                        //Yahoo,上车喽!!
                        person.getOn();
                        System.out.println("公交车"+name+"上了一位乘客");
                        //休眠100ms,才能再上下一位乘客
                        Thread.sleep(100);
                    }
                }
                System.out.println("公交车"+name+"开走了");
            } catch (InterruptedException ex) {
            }
        }
    }

}


在这里,情况有了不同,仍然还是一个火车站作为生产者,消费者变成了两辆公交车。两个消费者每隔10秒才启动一次,并消费完所有生产者生产的内容。这里有个注意点——定时启动的任务执行框架 ScheduledExecutorService。它具有延迟启动线程的方法schedule和按一定频率定时启动线程的方法scheduleAtFixedRate,因此可以简单实现“按一定频率去检查阻塞队列的任务,并执行相应任务”的目的。对于生产者生产数量不大,且实时性要求不高的系统,可以采用上述方案,每隔一个较长的时间10s启动一次消费者,消费者负责处理完阻塞队列中的所有任务;对于生产者数量不大,实时性要求较高,或者每个任务执行时间较长的系统,也可以采用下面改进做法:
        public void run() {
            Object obj = queue.poll();
            if (obj != null) {
                //业务流程
            }
        }
即消费者线程以一个较高的频率启动,比如每秒启动一个。每次启动都从阻塞队列中非阻塞的获取一个任务,如果任务不为空,则执行;否则什么也不做。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值