队列(一)——阻塞队列BlockingQueue

队列

其接口Queue,Queue继承自Collection,因此,队列也具备Collection的基本特征。多数的实现类位于java.util.concurrent,与线程池位于同一个包下,大多数的队列都与线程和锁挂钩,少量位于java.util,比如LinkedList、PriorityQueue。

程序员最早接触的队列一般是LinkedList,它经常被拿来和ArrayList比较,其实它不仅是一个List,同时也是一个Queue(双端队列)。它包含了offer(e)和poll()函数,允许以先进先出的方式进行数据存取,但是它并不是一个线程安全的Queue,不适合在多线程访问下使用。

阻塞队列

继承自BlockingQueue接口,BlockingQueue直译就是阻塞队列,根据文档介绍,阻塞队列的实现类是线程安全的,主要用于“生产者消费者”队列。

“生产者与消费者”是一个经典的多线程并发的案例,学习过Java的同学,应该听老师讲解过:同一个集合,多个线程负责添加数据,多个线程从中获取数据,所有线程都是同时执行的,如果集合已满,则生产者等待,如果集合为空,那么消费者等待。

在课上,我们通常会用到Synchronous关键字、Thread.wait()和Thread.notify()等函数,但是最终的代码相对复杂,不便于记忆和管理,使用阻塞队列,可以轻松地实现这些功能。

3个简单的阻塞队列:

  1. SynchronousQueue  译为同步队列,它也是一个阻塞队列。在代码的实现上,只允许存放一个元素,当存放第二个元素的时,线程进入等待,直到第一个元素被取出。这种设计,保证了生产线程和消费线程之间的同步关系。但是并不具备缓存的效果,它直接阻塞了生产线程,不适用于大数据的情况。
  2. ArrayBlockingQueue  定长数组阻塞队列,内部由数组实现,因为队列需要经常对数据进行存取、排序,其效率反而要低于链表阻塞队列,使用的时候必须指定长度。
  3. LinkedBlockingQueue  线性阻塞队列,内部由链表实现,不设置长度的情况,队列长度为Integer.MAX_VALUE,为防止队列过度扩展可以选择带参数的构造函数,其存取效率高于数组队列。

简单的消息轮询Demo

 

/**
 * 消息处理接口回调
 * 
 * @author ChenSS on 2018年1月30日
 */
public interface Handler<T> {
    void handleMessage(T msg) throws Exception;

    void handleException(T msg, Exception e);
}


import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 阻塞队列消息轮询
 * 
 * @author ChenSS on 2018年1月30日
 */
public class BlockingLopper<T> {
    private BlockingQueue<T> queue;
    /**
     * 哑元元素,在不强制中断线程的情况下,用于告知通知轮询线程可以结束循环
     */
    public T dummy;
    private Handler<T> handler;

    public void setQueue(BlockingQueue<T> queue) {
        this.queue = queue;
    }

    public void setDummy(T dummy) {
        this.dummy = dummy;
    }

    public void setHandler(Handler<T> handler) {
        this.handler = handler;
    }

    /**
     * 发送消息
     */
    public void put(T value) throws InterruptedException {
        queue.put(value);
    }

    /**
     * 发送消息
     * 
     * @throws IllegalStateException
     *             通常表示队列已满,不允许添加新的元素
     */
    public void add(T value) {
        queue.add(value);
    }

    /**
     * 发送消息
     * 
     * @throws RuntimeException
     *             代码存在部分未检查异常
     */
    public boolean offer(T value) {
        if (!queue.offer(value)) {
            try {
                // 简单的失败处理策略,当前线程等待0.5秒,重新尝试添加该元素
                // 以实际开发为准,在非必要的情况不要阻塞生产线程
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return this.offer(value);
        }
        return true;
    }

    /**
     * 发送消息
     * 
     * @throws InterruptedException
     *             等待时被中断
     */
    public void offer(T value, long timeout) throws InterruptedException {
        queue.offer(value, timeout, TimeUnit.MILLISECONDS);
    }

    /**
     * 放置哑元元素,通知线程结束(取出所有元素后结束)
     */
    public void stopLoop() throws InterruptedException {
        queue.put(this.dummy);
    }

    /**
     * 启动线程,开始轮询消息
     */
    public Thread loop() {
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                T t = null;
                for (;;) {
                    try {
                        t = queue.take();
                        if (dummy.equals(t)) {
                            return;
                        } else {
                            handler.handleMessage(t);
                        }
                    } catch (Exception e) {
                        handler.handleException(t, e);
                    }
                }
            }
        });
        thread.start();
        return thread;
    }

    /**
     * 启动守护线程,开始轮询消息
     */
    public Thread loopDeamon() {
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                T t = null;
                for (;;) {
                    try {
                        t = queue.take();
                        handler.handleMessage(t);
                    } catch (Exception e) {
                        handler.handleException(t, e);
                    }
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
        return thread;
    }
}
import java.util.concurrent.ArrayBlockingQueue;
/**
 * 测试函数
 */
public class Test {
    public static void main(String[] args) throws InterruptedException {
        Handler<String> handler = new Handler<String>() {

            @Override
            public void handleMessage(String msg) throws Exception {
                Thread.sleep(1000);
                System.out.println(msg);
            }

            @Override
            public void handleException(String msg, Exception e) {
                e.printStackTrace();
            }
        };

        BlockingLopper<String> lopper = new BlockingLopper<String>();

        lopper.setDummy(new String());
        lopper.setHandler(handler);
        //指定不同的阻塞队列进行测试
        lopper.setQueue(new ArrayBlockingQueue<>(3));
        lopper.loop();

        lopper.offer("AAAA");
        lopper.offer("BBBB");
        lopper.offer("CCCC");
        lopper.offer("DDDD");
        System.out.println("--------------------");
        lopper.stopLoop();
    }
}

 

常用函数

常用函数表

  抛出异常 特殊值 阻塞  超时 
插入  add(e)  offer(e)  put(e)  offer(e, time, unit) 
移除  remove()  poll() take() poll(time, unit) 
检查 element()  peek()  不可用  不可用

 介绍

存值

  1. add(e) 实际就是Collection接口下的add()方法,当我们所添加的元素不允许被添加到队列中,抛出IllegalStateException异常。
  2. offer(e) 用于添加新的元素,当使用有容量限制的队列时,此方法通常优于add()。在使用offer()添加元素的情况下,如果返回false,此时程序是正常执行的,仅仅只是因为超出了队列容量,无法添加新的数据;而代码如果报错,则说明代码存在某些未检查的异常,代码可能需要进一步优化。
  3. put(e) 同样用于添加新的元素,阻塞队列特有的函数,如果队列已满,将阻塞线程,直到队列元素被取出,存在可用的空间。
  4. offer(e, time, unit)做一定的延迟,再添加元素,阻塞队列特有的函数。此函数适用于,取值线程需要时间完成初始化的情形,它的延迟不一定是绝对的延迟,当存在等待取值的线程时,会立刻执行此函数。如果超出延迟时间,队列依然没有多余空间,则添加失败,返回false。

取值

  1. remove() 和 poll() 方法可移除并返回队列的头,仅在队列为空时其行为有所不同:remove() 方法抛出一个异常,而 poll() 方法则返回 null。
  2. take()方法同样用于取值,阻塞队列特有的函数,如果队列为空,线程将阻塞直到有新的元素被添加到队列中。
  3. poll(time, unit)与offer(e, time, unit)函数相对应,用在存值线程需要时间初始化的情形,他会尝试等待一段时间,如果队列提前有值,则立刻跳过等待。

取值而不移除(校验)

  1. element()和peek()用于数据检测,仅仅只是取值,但是不删除元素,通常用于一些元素的校验。

 

posted on 2018-02-02 10:33  疯狂的妞妞  阅读(309)  评论(0编辑  收藏  举报

导航