队列
其接口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个简单的阻塞队列:
- SynchronousQueue 译为同步队列,它也是一个阻塞队列。在代码的实现上,只允许存放一个元素,当存放第二个元素的时,线程进入等待,直到第一个元素被取出。这种设计,保证了生产线程和消费线程之间的同步关系。但是并不具备缓存的效果,它直接阻塞了生产线程,不适用于大数据的情况。
- ArrayBlockingQueue 定长数组阻塞队列,内部由数组实现,因为队列需要经常对数据进行存取、排序,其效率反而要低于链表阻塞队列,使用的时候必须指定长度。
- LinkedBlockingQueue 线性阻塞队列,内部由链表实现,不设置长度的情况,队列长度为Integer.MAX_VALUE,为防止队列过度扩展可以选择带参数的构造函数,其存取效率高于数组队列。
简单的消息轮询Demo
/** * 消息处理接口回调 * * @author ChenSS on 2018年1月30日 */ public interface Handler{ 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 { private BlockingQueue queue; /** * 哑元元素,在不强制中断线程的情况下,用于告知通知轮询线程可以结束循环 */ public T dummy; private Handler handler; public void setQueue(BlockingQueue queue) { this.queue = queue; } public void setDummy(T dummy) { this.dummy = dummy; } public void setHandler(Handler 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 handler = new Handler () { @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 lopper = new BlockingLopper (); 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() | 不可用 | 不可用 |
介绍
存值
- add(e) 实际就是Collection接口下的add()方法,当我们所添加的元素不允许被添加到队列中,抛出IllegalStateException异常。
- offer(e) 用于添加新的元素,当使用有容量限制的队列时,此方法通常优于add()。在使用offer()添加元素的情况下,如果返回false,此时程序是正常执行的,仅仅只是因为超出了队列容量,无法添加新的数据;而代码如果报错,则说明代码存在某些未检查的异常,代码可能需要进一步优化。
- put(e) 同样用于添加新的元素,阻塞队列特有的函数,如果队列已满,将阻塞线程,直到队列元素被取出,存在可用的空间。
- offer(e, time, unit)做一定的延迟,再添加元素,阻塞队列特有的函数。此函数适用于,取值线程需要时间完成初始化的情形,它的延迟不一定是绝对的延迟,当存在等待取值的线程时,会立刻执行此函数。如果超出延迟时间,队列依然没有多余空间,则添加失败,返回false。
取值
- remove() 和 poll() 方法可移除并返回队列的头,仅在队列为空时其行为有所不同:remove() 方法抛出一个异常,而 poll() 方法则返回 null。
- take()方法同样用于取值,阻塞队列特有的函数,如果队列为空,线程将阻塞直到有新的元素被添加到队列中。
- poll(time, unit)与offer(e, time, unit)函数相对应,用在存值线程需要时间初始化的情形,他会尝试等待一段时间,如果队列提前有值,则立刻跳过等待。
取值而不移除(校验)
- element()和peek()用于数据检测,仅仅只是取值,但是不删除元素,通常用于一些元素的校验。