常用的并发集合有如下几种:
一、ConcurrentHashMap
ConcurrentHashMap
是一个线程安全的 HashMap 实现。
工作原理
ConcurrentHashMap在JDK 1.7的实现原理:
在JDK 1.7中,ConcurrentHashMap采用了一种叫做**分段锁(Segmentation Lock)**的技术。它内部包含多个Segment,每个Segment都拥有一个锁。当线程访问ConcurrentHashMap时,会根据哈希算法计算出应该访问哪个Segment,然后只锁定该Segment进行操作,其他线程访问其他Segment则不受影响,从而实现真正的并发访问。每个Segment都维护着一个HashEntry数组,用于存储键值对数据。
ConcurrentHashMap在JDK 1.8的实现原理:
在JDK 1.8中,ConcurrentHashMap的实现原理发生了较大的变化,它摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用CAS算法和synchronized,使得多个线程可以在不同段上进行并发操作,从而实现在高并发场景下的高性能。当链表长度大于一定阈值(默认为8)时,链表转化为红黑树,进一步减少搜索时间。
使用场景
ConcurrentHashMap
适用于需要高并发的场景,尤其是读多写少,或者读写都很多的场景。它的主要优势在于可以支持多个线程并发写入,因此,当我们需要在并发环境中使用Map时,它是很好的选择。例如:
- 缓存:在创建缓存系统时,
ConcurrentHashMap
是一个很好的选择,因为它可以处理高并发的数据访问。 - 数据库连接池:数据库连接池通常会使用
ConcurrentHashMap
来存储和管理连接对象,以支持多线程并发获取或释放数据库连接。 - 统计系统:例如网站统计用户的访问量,可以用
ConcurrentHashMap
记录用户的访问情况,由于是多线程的,可以支持更高的并发。
二、CopyOnWriteArrayList
和 CopyOnWriteArraySet
CopyOnWriteArrayList
和 CopyOnWriteArraySet
是线程安全的 List 和 Set 实现,它们通过每次修改时复制底层数组来实现线程安全。
工作原理
CopyOnWriteArrayList
在所有的修改方法执行之前,首先要获取锁
在使用元素添加方法时,每次会拷贝出一个长度 +1 的新数组,同时把要添加的元素放入到新数组的最后一个位置上,最后把旧数组替换成新数组
在使用元素移除方法时,每次会拷贝出一个长度 -1 的新数组,最后根据要删除的元素位置,进行数组拷贝,最后把旧数组替换成新数组
所有读取方法都没有采取任何同步措施,包括获取迭代器
获取迭代器的方法,并没有像很多文章中说的对当前数组进行了一个拷贝,而是直接把当前数组的引用传递给了迭代器
之所以不用对当前数组进行拷贝,是因为每次的修改方法都会创建出一个新数组,所以 CopyOnWriteArrayList 迭代器根本不会,也没有必要去拷贝当前数组
CopyOnWriteArrayList 的主要工作原理就是 "写时复制" (Copy-On-Write)。这意味着所有的修改操作(如:add、set 和 remove)都是在底层的数组的一个副本上进行的。这个原理帮助我们解决了并发环境下的修改冲突问题。
具体来说,当我们调用一个修改操作时,CopyOnWriteArrayList 会首先锁定它的内部锁,然后复制底层的数组,接着在新的数组副本上进行修改,最后再将引用从原来的数组切换到新的数组。由于所有的修改操作都是在数组的副本上进行的,所以读操作完全不需要锁定,可以直接读取。这样就大大提高了并发读的性能。
然而,由于每次修改都需要复制整个数组,所以写操作的性能和内存开销都比较大。尤其是当数组的大小非常大时,写操作的开销就变得更加显著。这也是为什么我们通常只在读操作远多于写操作的场景中使用 CopyOnWriteArrayList。
CopyOnWriteArraySet
CopyOnWriteArraySet 是一个并发安全的无重复集合,底层是基于 CopyOnWriteArrayList 来实现
使用场景
由于 CopyOnWriteArrayList 的特性——读取高效、写入时复制整个底层数组,因此它非常适用于读操作远多于写操作的场景。例如,实时读取系统配置信息的场景。系统配置通常只在系统启动或管理员修改时更新,而在系统运行时,可能会有大量的读取操作,此时使用 CopyOnWriteArrayList 能够提供很高的性能。
再比如,对于事件监听器的管理,我们通常会在应用启动时添加大部分监听器,而在运行过程中,这些监听器的列表很少改动,大部分操作是遍历这个列表来通知所有监听器。这种场景下,CopyOnWriteArrayList 也是非常合适的。
三、BlockingQueue
BlockingQueue
描述了一个阻塞队列应该有的行为,例如:
- 阻塞读方法:
take()
- 阻塞写方法:
put()
阻塞的读写方法的函数签名上都会抛出 InterruptedException
,具体的阻塞逻辑由实现类自行实现。
BlockingQueue
接口及其实现类(如 ArrayBlockingQueue
、LinkedBlockingQueue
、PriorityBlockingQueue
等)为并发编程提供了线程安全的队列结构。
ArrayBlockingQueue
是一个基于数组实现的阻塞队列,创建对象时必须指定容量
LinkedBlockingQueue
是一个基于单向链表实现的阻塞队列,其容量为:
如果在构造函数中指定了容量,那么容量就是队列长度的最大边界限制
如果没有在构造函数中指定容量,容量将会默认设置成 Integer.MAX_VALUE,近似无界
所以,LinkedBlockingQueue 在不传入容量的情况下,最大容量会被设置为 Integer.MAX_VALUE
PriorityBlockingQueue
PriorityBlockingQueue 底层有一个可扩容的对象数组 Object[] queue,结构是堆
内置一个 ReentrantLock 独占锁,以及由这个锁构造出来的 Condition notEmpty 条件
一个 volatile 关键字修饰的整型变量 volatile int allocationSpinLock