阻塞虽然是不好的,但是我们有时候不得不阻塞,比如说一家餐厅人满了,店主当然不希望即将过来的这些客人全部都走,而是希望它们留下来继续排队等候。对于阻塞的内容,线程和资源的调度管理能力,这样就出了一个接口,就叫阻塞队列。在不得不阻塞,必须要阻塞的时候,如何管理好被阻塞的资源和线程,这就是阻塞队列。
队列的特性:先进先出,插入元素在队尾,删除元素在队头。
阻塞:必须要阻塞/不得不阻塞
阻塞队列是一个队列,在数据结构中中起的作用如下图:
线程1往阻塞队列里添加元素,线程2从阻塞队列里移除元素。
阻塞队列,我们可以简单的理解为海底捞火锅店的候客区,如图绿框框就是我们的阻塞队列,即我们所理解的海底捞火锅店的候客区,里面等着的就是一桌一桌的食客。如果候客区也满了,还加不加人?
当队列是空的,从队列中获取元素的操作将会被阻塞。
当队列是满的,从队列中添加元素的操作将会被阻塞。
试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。
对于上面阻塞的问题,我们又可以举一个例子,假如说Thread1是做蛋糕的师傅,Thread2是买蛋糕的顾客,而BlockingQueue是食品柜。当顾客来了,发现食品柜是空的,想消费蛋糕的操作将会被阻塞。如果蛋糕买的人少了,食品柜中的蛋糕满了,此时生产蛋糕的操作将会被阻塞。
**试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。**简单的说,就是空的时候不能消费,满的时候不能增加。
试图向已满的队列中添加新元素的线程将会被阻塞,直到其它线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增。
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起。
wait就是一种阻塞,notify是一种唤醒,await是一种阻塞, signal是唤醒。
为什么需要 BlockingQueue?
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue 都给你一手包办了。在 concurrent 包发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
也就是不用手写wait和notify了。只要柜子里蛋糕满了,生产线程就会阻塞,只要柜子里没有蛋糕了,消费线程就会被阻塞。
BlockingQueue所有已知实现类:
ArrayBlockingQueue , DelayQueue , LinkedBlockingDeque , LinkedBlockingQueue , LinkedTransferQueue , PriorityBlockingQueue , SynchronousQueue
在我们以前学习集合的过程中,Collection接口有两个常用的接口List接口和Set接口,现在又有了阻塞队列接口BlockingQueue
ArrayBlockingQueue:由数组结构组成的有界阻塞队列
LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列
PriorityBlockingQueue:支持优先级排序的无界阻塞队列
DelayQueue:使用优先级队列实现的延迟无界阻塞队列
SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列
LinkedTransferQueue:由链表结构组成的无界阻塞队列
LinkedBlockingDeque:由链表结构组成的双向阻塞队列
核心方法如下:
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | put(time,unit) |
移除 | element() | peek() | 不可用 | 不可用 |
共同点:插入成功或者删除成功都返回true(除了put和take),区别在于处理异常情况
(1)抛出异常的方法
当阻塞队列满时,再往队列里add元素会抛出IllegalStateException:Queue full;
当阻塞队列空时,再从队列里remove移除元素会抛NoSuchElementException。
(2)返回特殊值(boolean值)的方法
插入成功,返回true;插入失败,返回false;
删除成功返回出队列元素;删除失败返回null;
(3)阻塞的方法(添加无返回值)
当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出。
当阻塞队列空时,消费者线程试图take队列里的元素,队列会一直阻塞消费者线程直到队列有可用元素。
(4)超时的方法
当向阻塞队列offer元素时候,时间超过了设定的值,就会出现超时中断;
当向阻塞队列poll元素时候,时间超过了设定的值,就会出现超时中断。
public static void main(String[] args) {
//ArrayBlockingQueue是由数组结构组成的有界阻塞队列,所以要加参数
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
// System.out.println(blockingQueue.add("d"));
}
public static void main(String[] args) {
//ArrayBlockingQueue是由数组结构组成的有界阻塞队列,所以要加参数
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
System.out.println(blockingQueue.add("d"));
}
当阻塞队列满时,再往队列里add元素会抛出IllegalStateException:Queue full;
public static void main(String[] args) {
//ArrayBlockingQueue是由数组结构组成的有界阻塞队列,所以要加参数
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//remove会返回被移除的对象
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
}
当阻塞队列空时,再从队列里remove移除元素会抛NoSuchElementException。
public static void main(String[] args) {
//ArrayBlockingQueue是由数组结构组成的有界阻塞队列,所以要加参数
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("a"));
//element就是查看一下队首元素的值
System.out.println(blockingQueue.element());
}
public static void main(String[] args) {
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d"));
}
插入成功,返回true;插入失败,返回false;
public static void main(String[] args) {
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
}
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("a");
blockingQueue.put("a");
blockingQueue.put("a");
}
没有报错,当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出。,即腾出空位置为止,能塞进去。与semaphore争车位类似
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("a");
blockingQueue.put("a");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
}
当阻塞队列空时,消费者线程试图take队列里的元素,队列会一直阻塞消费者线程直到队列有可用元素。
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a",3L,TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a",3L,TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a",3L,TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a",3L,TimeUnit.SECONDS));
}
程序执行结果如下:当往阻塞队列添加第六个元素的时候,队列添加不进去,3秒后会超时中断。
当向阻塞队列offer元素时候,时间超过了设定的值,就会出现超时中断;
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a",3L,TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a",3L,TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a",3L,TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS));
}
当向阻塞队列poll元素时候,时间超过了设定的值,就会出现超时中断。