通过前面几篇文章的学习, 我们已经知道了 Java 中的队列分为阻塞队列和非阻塞队列以及常用的七个阻塞队列. 如下图:
在查看以上七个队列的 API 的时候, 我们可以很明显的看到以下四组 API:
- add()/remove()/remove
- offer()/poll()/peek()
- put/take()
- offer(e,time,unit)/poll(time,unit).
分别对应的是, 添加元素和移除元素以及检查队首元素.
这四组 API 各有什么不同呢? 凯哥把这四组 API 看作是人的一生四个阶段, 分别是:
少年时期, 初生牛犊不怕虎, 一言不合就开干, 对应的是第一组 API: 会抛异常的 API;
青年时期, 吸取各方面的知识, 为人处事会圆滑, 对应的是第二组 API: 有返回值, 不抛出异常的;
中年时期, 三十而立, 咬定青山不放松, 对应的是第三组 API: 阻塞, 一直等待;
老年时期, 看透人生, 顺其自然, 对应的是第四组 API: 阻塞, 当到了预定的超时时间, 退出.
下面我们就来详细讲解这四组 API
第一组 API, 会抛出异常的: 一言不合就开干
添加元素: add(e):
当队列未满的时候, 向队列中添加元素正常; 当队列满的时候, 再向队列中添加元素的话, 会抛出 throw new IllegalStateException("Queue full"); 异常.
代码演示及运行结果:
源码分析:
从源码中, 我们可以看到, 调用的是 offer(e) 方法, 在下文中, 我们也会讲解到的. 如果 offer 方法返回 true 的话, 就直接返回, 否则就抛出: throw new IllegalStateException("Queue full"); 异常的.
删除元素: remove()
当队列不为空的时候, 调用该方法, 返回被移除的元素; 当队列为空的时候在调用该方法, 会抛出异常.
来看看源码:
源码中调用了 poll 方法, 当获取到的对象不为空的时候, 返回获取到的对象; 如果为空的话, 就抛出: throw new NoSuchElementException(); 异常.
判断当前队列的队首元素: element()
该方法是获取队首元素的. 当队列不为空的时候, 返回队列中当前队首元素; 如果队列为空的时候, 调用该方法会抛异常的.
我们来看看源码:
获取队首元素代码演示及运行结果如下图:
第一组 API 三个方法我们都讲解完了. Add/remove/element 方法. 最大的特点就是, 队列为空或者是队列满了, 继续操作队列的话, 就会抛出异常. 这个凯哥根据就像我们人的一生中少年时期一样, 初生牛犊不怕虎, 遇到什么不服的或者是不顺心的就暴躁了, 碰不得, 一碰就爆炸. 一言不合就开干!
第二组: 带有返回值的, 不会抛出异常: 为人处事会圆滑了
第二组 API 的不像第一组那么暴躁如雷了, 不想就抛异常. 第二组, 不会抛出异常了. 我们接着来看看:
添加元素: offer(e)
需要主要: 这里的 offer 方法只有一个参数, 这个和我们后面讲解的一组的区别
当队列未满的时候, 向队列中添加元素, 返回 true; 当队列已经满了, 继续向队列中添加元素的话, 不会抛出异常, 会返回 false.
源码分析:
从源码中, 我们可以看到, offer(e) 的方法中, 有个 count 计数器, 每次添加元素后, 都会 count++. 当 count 的值等于队列的长度的时候, 返回 false. 而不是抛出异常. 我们来用代码演示.
Offer(e) 添加元素代码演示及运行结果:
删除元素: poll()
注意: 参数为空哦!
当队列不为空的时候, 返回被移除的元素, 当队列为空的时候, 返回 null. 而不是抛出异常.
源码分析:
从源码中, 我们将看到 count 这个计数器又起作用了. 先判断 count 是否 ==0
如果不等于 0, 调用 dequeue 方法, count--, 然后将获取到的元素返回;
如果 count == 0 的话, 直接返回 null.
源码如下图:
代码演示及运行结果:
获取队首元素: peek()
当队列不为空的时候, 返回当前队列的队首元素; 如果队列为空的时候, 返回 null, 而不是抛出异常.
源码分析:
在源码中, 我们可以看到调用了 itemAt(takeIndex) 方法. 但是在这个方法后面有这么已经注释: null when queue is empty. 源码如下图:
代码演示:
从运行的结果, 我们可以看到, 当移除最后一个元素: kaigejava 的时候, 获取到的队首元素已经为 null 了. 因为队列为空了, 所以, 就算后面还有循环, 获取到的队首元素依然是 null, 而不是抛出异常. 运行结果如下图:
从第二组 API 中, 我们可以看到, 不像第一组那么极端了. 当队列为空或者是队列满的时候, 返回数据告知对象. 这个就像我们人生由少年时代, 进入了青年时代, 经过学校的洗礼之后, 为人处事学会了圆滑了.
接下来, 我们就该进入人生第三个阶段: 中年时代, 我们一起来看看这个阶段的 API 又是什么样子的
第三组: 阻塞, 一直等待: 三十而立, 咬定青山不放松
第三组 API, 相对于第一组和第二组最大的区别就是: 第三组会等待着, 如果不被中断, 就会等到天荒地老.
添加元素: put(e)
当队列满的时候, 进入阻塞等待状态, 一直等待, 直到可以添加到队列中为止.
需要说明: 在阻塞等待过程中, 有可能会被中断, 所以会抛出中断异常: throws InterruptedException.
我们先来看看源码:
在源码中, 我们会看到 while 循环来判断 count 的值是否等于队列的长度, 如果不等于, 就 enqueue. 然后 count++; 如果 count 的值等于队列的长度的是, 就调用 notFull.await() 方法, 而 notfull 是 condition 对象. 在之前的文章学习中, 我们知道 coditon.await() 方法会进入阻塞状态. 源码如下图:
代码演示及运行结果:
我们可以看到, 当添加第四个元素的时候, 队列进入了阻塞状态. 如下图:
删除元素: take()
当队列不为空的时候, 返回被移除的元素; 当队列为空的时候, 进入阻塞等待状态.
源码分析:
代码演示:
这一组队列, 就像进入中年时期的我们一样, 三十而立, 要好好工作, 努力工作. 只要天不塌, 地不陷, 一直工作着.
第四组: 带有等待超时的阻塞 API
如果第三组 API 一直阻塞等待着, 你受不了的话, 并发大师还为我们准备了第四组 API, 带有超时时间的
添加元素: offer(e,time,unit)
参数说明:
e: 将要被添加到队列中的元素
time:long 类型的. 预设定的需要等待的时间
unit:TimeUnit. 超时时间的单位
来看看源码:
从源码中我们将会看到:
判断 count 的值是否等于队列的长度
如果不等于, 调用 enqueue 方法, 然后 count++, 返回 true.
如果 count== 队列的长度的时候, 判断设置的等待超时时间是否小于等于 0
如果等待的时间大于 0 的话, 进入 notFull.awaitNanos 方法中进行阻塞等待.
在前面文章中, 我们详细讲解过 condition.awaitNanos 这个方法. 这里就不再赘述了.
当等待的时间小于等于 0 的时候, 就返回 false.
源码如下图:
代码演示:
删除元素: poll(time,unit)
当队列为空的时候, 进入阻塞等待, 等到超时时间的时候, 返回 null. 退出等待.
代码演示:
第四组 API 带有等待超时时间, 就像是我们人的一生老年时期, 看透一切了. 一切都是顺其自然了, 不再争强好胜了.
总结:
凯哥通过人的一生四个阶段来比喻这四组 API 是为了让大家更好记忆. 接下来, 我们来进行总结:
欢迎来聊!~
来源: https://www.cnblogs.com/kaigejava/p/12816847.html