Stack
Stack
是 Vector 的一个子类,它实现了一个标准的后进先出的栈。其具备 List 所有方法。
特有功能
- E
push
(E item):压栈。相当于add(E e)
- E
pop()
:出栈。相当于remove()
- E
peek()
:查看栈顶元素,不删除 - boolean
empty()
:判断栈是否为空
Stack stack = new Stack<String>();
/* 压栈 */
stack.push("11");
stack.push("22");
stack.add("33");
/* 查看栈顶元素不删除 */
System.out.println(stack.peek());
System.out.println(stack);
/* 出栈 */
System.out.println(stack.pop());
System.out.println(stack.remove(0));
System.out.println(stack);
/* 判断栈是否为空 */
System.out.println(stack.empty());
push()
和add()
方法都是向栈顶添加元素,pop()
取出栈顶元素并删除,remove(index)
返回并删除指定下标的元素。
Queue
Java集合中的Queue
继承自Collection接口,Deque
,LinkedList
,PriorityQueue
,BlockingQueue
等类都实现了它。先进先出。
Queue 用来存放等待处理元素的集合,这种场景一般用于缓冲、并发访问。
特有功能
- boolean
offer(E e)
:入队列。相当于add(E e)
- E
poll()
:出队列。相当于remove()
- E
peek()
:得到队首元素,不删除。相当于element()
- boolean
isEmpty()
:判断栈是否为空
LinkedList
类实现了 Queue
接口,因此我们可以把 LinkedList 当成 Queue 来用。
LinkedList linkedList = new LinkedList<String>();
/* 入队列 */
linkedList.offer("11");
linkedList.offer("22");
linkedList.add("33");
/* 获取队首元素 */
System.out.println(linkedList.peek());
System.out.println(linkedList);
/* 出队列 */
System.out.println(linkedList.poll());
System.out.println(linkedList.remove());
System.out.println(linkedList);
/* 判断队列是否为空 */
System.out.println(linkedList.isEmpty());
Deque
Deque
是一个双端队列接口,继承自Queue
接口,Deque的实现类是LinkedList
、ArrayDeque
、LinkedBlockingDeque
,其中LinkedList是最常用的。
特有功能
- boolean
offerFirst(E e)
:从队头入队 - boolean
offerLast(E e)
:从队尾入队 - E
pollFirst()
:从队头出队 - E
pollLast()
:从队尾出队 - E
peekFirst()
:得到队头元素,不删除 - E
peekLast()
:得到队尾元素,不删除
此接口定义在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。插入操作的后一种形式是专为使用有容量限制的 Deque 实现设计的;在大多数实现中,插入操作不能失败。
BlockingQueue
BlockingQueue
很好的解决了多线程中,如何高效安全 “传输” 数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。
多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者
”和“消费者
”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者和消费者在某个时间段内,发生数据处理速度不匹配的情况,理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。
在concurrent
包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。强大的concurrent包横空出世了,而它也给我们带来了强大的BlockingQueue。下面两幅图演示了BlockingQueue的两个常见阻塞场景:
💦 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。
💦 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。
这也是我们在多线程环境下,为什么需要BlockingQueue
的原因。作为BlockingQueue
的使用者,我们再也不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue
都给你一手包办了。既然BlockingQueue
如此神通广大,让我们一起来见识下它的常用方法。
核心方法
添加数据到队列
- boolean
offer(E e)
:将 e 加到 BlockingQueue 里,即如果 BlockingQueue 可以容纳,则返回 true,否则返回 false(本方法不阻塞当前执行方法的线程)。 offer(E e, long timeout, TimeUnit unit)
:可以设定等待的时间,如果在指定的时间内,还不能往队列中加入 e,则返回 false。put(E e)
:把 e 加到 BlockingQueue 里,如果 BlockQueue 没有空间,则调用此方法的线程被阻塞,直到 BlockingQueue 里面有空间再继续。add(E e)
:如果可以在不违反容量限制的情况下立即将指定元素插入此队列,则在成功时返回 true ,如果当前没有可用空间则IllegalStateException
。 当使用容量受限的队列时,通常最好使用offer
读取队列
poll(long timeout, TimeUnit unit)
:从BlockingQueue
取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则时间超时还没有数据可取,返回 false。take()
:取走BlockingQueue
里排在首位的对象,若BlockingQueue
为空,阻塞 进入等待状态直到BlockingQueue
有新的数据被加入。drainTo(Collection<? super E> c)
:一次性从BlockingQueue
获取所有可用的数据对象(还可以指定获取数据的个数drainTo(Collection<? super E> c, int maxElements)),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
删除数据
remove(Object o)
:从此队列中移除指定元素的单个实例(如果存在)。则返回true 。
可以删除 BlockingQueue 中的所有元素,而不仅仅是开始和结束的元素。比如说,你将一个对象放入队列之中以等待处理,但你的应用想要将其取消掉。那么你可以调用诸如 remove(o)
方法来将队列之中的特定对象进行移除。但是这么干效率并不高,除非你确实不得不那么做。
常见BlockingQueue
1. ArrayBlockingQueue
基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
2. LinkedBlockingQueue
基于链表的阻塞队列,同ArrayBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成)。只有当队列缓冲区达到最大缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者线程。
而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。
3. DelayQueue
DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
4. PriorityBlockingQueue
基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定)
5. SynchronousQueue
一种无缓冲的等待队列,相对于有缓冲的BlockingQueue来说,少了一个中间经销商的环节(缓冲区)。
小结
BlockingQueue不光实现了一个完整队列所具有的基本功能,同时在多线程环境下,他还自动管理了多线间的自动等待与唤醒功能,从而使得程序员可以忽略这些细节,关注更高级的功能。