阻塞队列简介
阻塞队列的历史
📝 通俗解释:
在 JDK 1.5 之前,想写一个“生产者-消费者”模型(比如一个线程造包子,一个线程吃包子),需要自己用wait()和notify()写很多复杂的代码,很容易出错(比如死锁)。
JDK 1.5 之后,Java 官方直接给了现成的工具箱(JUC包),里面就有ArrayBlockingQueue等“阻塞队列”。
你只需要管“放”和“拿”,剩下的排队、等待、唤醒,它都帮你搞定了。
📝 通俗解释:
在 JDK 1.5 之前,想写一个“生产者-消费者”模型(比如一个线程造包子,一个线程吃包子),需要自己用wait()和notify()写很多复杂的代码,很容易出错(比如死锁)。
JDK 1.5 之后,Java 官方直接给了现成的工具箱(JUC包),里面就有ArrayBlockingQueue等“阻塞队列”。
你只需要管“放”和“拿”,剩下的排队、等待、唤醒,它都帮你搞定了。
ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
📝 通俗解释:
- 是什么:ArrayList 就是个会自动扩容的数组。
- 比喻:普通的数组像定制定做的柜子,做好了就不能改大小。ArrayList 像神奇的魔法柜子,东西放满了,它自动变大(扩容),让你能继续放。
- 底层:虽然它对外表现得能变大,其实内部还是用的普通数组(
Object[])。“变大”的戏法是:造个更大的新数组,把旧数据搬过去。
本文来自末读代码投稿:https://mp.weixin.qq.com/s/AHWzboztt53ZfFZmsSnMSw ,JavaGuide 对原文进行了大篇幅改进优化。
上一篇文章介绍了 HashMap 源码,反响不错,也有很多同学发表了自己的观点,这次又来了,这次是 ConcurrentHashMap 了,作为线程安全的 HashMap ,它的使用频率也是很高。那么它的存储结构和实现原理是怎么样的呢?
在 JDK1.5 之前,如果想要使用并发安全的 List 只能选择 Vector。而 Vector 是一种老旧的集合,已经被淘汰。Vector 对于增删改查等方法基本都加了 synchronized,这种方式虽然能够保证同步,但这相当于对整个 Vector 加上了一把大锁,使得每个方法执行的时候都要去获得锁,导致性能非常低下。
JDK1.5 引入了 Java.util.concurrent(JUC)包,其中提供了很多线程安全且并发性能良好的容器,其中唯一的线程安全 List 实现就是 CopyOnWriteArrayList 。关于java.util.concurrent 包下常见并发容器的总结,可以看我写的这篇文章:Java 常见并发容器总结 。
DelayQueue 是 JUC 包(java.util.concurrent)为我们提供的延迟队列,用于实现延时任务比如订单下单 15 分钟未支付直接取消。它是 BlockingQueue 的一种,底层是一个基于 PriorityQueue 实现的一个无界队列,是线程安全的。关于PriorityQueue可以参考笔者编写的这篇文章:PriorityQueue 源码分析 。
感谢 changfubai 对本文的改进做出的贡献!
HashMap 主要用来存放键值对,它基于哈希表的 Map 接口实现,是常用的 Java 集合之一,是非线程安全的。
HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个
这篇文章我根据《阿里巴巴 Java 开发手册》总结了关于集合使用常见的注意事项以及其具体原理。
强烈建议小伙伴们多多阅读几遍,避免自己写代码的时候出现这些低级的问题。
《阿里巴巴 Java 开发手册》的描述如下:
判断所有集合内部的元素是否为空,使用
isEmpty()方法,而不是size()==0的方式。
📝 通俗解释:
- 场景:你想知道箱子(集合)是不是空的。
- size() == 0:相当于把箱子里的东西一个个拿出来数一遍,数到0个才说空。如果是链表,可能要从头跑到尾,效率低。
- isEmpty():相当于只看箱子底部有没有东西,一眼就能看出来,效率高。
- 结论:一定要用
isEmpty(),又快又准。
Java 集合,也叫作容器,主要是由两大接口派生而来:一个是 Collection接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。对于Collection 接口,下面又有三个主要的子接口:List、Set 、 Queue。
Java 集合框架如下图所示:

HashMap 是非线程安全的,Hashtable 是线程安全的,因为 Hashtable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);HashMap 要比 Hashtable 效率高一点。另外,Hashtable 基本被淘汰,不要在代码中使用它;HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间(后文中我会结合源码对这一过程进行分析)。Hashtable 没有这样的机制。HashMap 对哈希值进行了高位和低位的混合扰动处理以减少冲突,而 Hashtable 直接使用键的 hashCode() 值。LinkedHashMap 是 Java 提供的一个集合类,它继承自 HashMap,并在 HashMap 基础上维护一条双向链表,使得具备如下特性: