Java并发43:并发集合系列-序章
这些转载文章的叙述角度各不相同,不过不影响我们通过这些文章对并发集合有一个初步的理解。
一:集合
编程,离不开数据结构。
JDK提供了Java集合框架(Java Collections framework),它包括可以用来实现多种不同的数据结构的接口、类和算法,如HaspMap、ArrayList等等。
我们在使用集合框架的时候,需要十分小心以保证其多线程的安全性,因为大多数集合类并没有对并发访问进行控制。
二:并发集合
为了解决这些集合框架造成的安全性问题,JDK逐渐提供了越来越多的并发集合类型。
我们在并发环境中,使用这些并发集合,不会产生数据不一致的问题。
2.1阻塞与非阻塞
JDK提供我们的并发集合类型,按照阻塞方式分为两种:
- 阻塞队列
- 包含添加操作:如果不能立即进行添加,则是因为集合已满;执行该操作的线程将被阻塞,直到添加成功。
- 包含删除操作:如果不能立即进行删除,则是因为集合已空;执行该操作的线程将被阻塞,直到删除成功。
- 非阻塞队列
- 包含添加操作:如果不能立即进行添加,则将返回null值或抛出异常。
- 包含删除操作:如果不能立即进行删除,则将返回null值或抛出异常。
2.2集合类型
JDK提供我们的并发集合类型,按照集合类型分为以下五种:
(1)List 列表
- CopyOnWriteArrayList :
- 基于写时复制(Copy-On-Write)思想实现。
- 适用于读多写少的并发场景,如黑名单,白名单等等。
- 并发读的性能高;并发写的空间消耗大。
- 只能保证数据的最终一致性,而非实时一致性。
- 读不会阻塞,写会阻塞。
- 链接:Java并发44:并发集合系列-基于写时复制的CopyOnWriteArrayList和CopyOnWriteArraySet
(2)Set 集合
- CopyOnWriteArraySet :
- 基于写时复制(Copy-On-Write)思想实现。
- 适用于读多写少的并发场景,如黑名单,白名单等等。
- 并发读的性能高;并发写的空间消耗大。
- 只能保证数据的最终一致性,而非实时一致性。
- 读不会阻塞,写会阻塞。
- 链接:Java并发44:并发集合系列-基于写时复制的CopyOnWriteArrayList和CopyOnWriteArraySet
- ConcurrentSkipListSet :
- 基于跳表(SkipList)实现。
- 跳表(SkipList)可以用来替代红黑树,使用概率均衡技术,插入、删除操作更简单、更快。
- 跳表(SkipList)本质上是以空间换取时间。
- ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的。
- ConcurrentSkipListMap中的元素是key-value键值对,而ConcurrentSkipListSet只用到了前者中的key。
- 链接:Java并发45:并发集合系列-基于跳表的ConcurrentSkipListSet和ConcurrentSkipListMap
(3)Map 映射
- ConcurrentSkipListMap :
- 基于跳表(SkipList)实现。
- 跳表(SkipList)可以用来替代红黑树,使用概率均衡技术,插入、删除操作更简单、更快。
- 跳表(SkipList)本质上是以空间换取时间。
- ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的。
- ConcurrentSkipListMap中的元素是key-value键值对,而ConcurrentSkipListSet只用到了前者中的key。
- 链接:Java并发45:并发集合系列-基于跳表的ConcurrentSkipListSet和ConcurrentSkipListMap
- ConcurrentHashMap :
- 基于所使用的锁分段技术实现,减小锁粒度,减少数据竞争的概率。
- get操作高效之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读。
- get操作的高效的根本原因在于采用了volatile关键字来保持多线程可见性。
- put操作之前首先需要进行Segment加锁;但不会影响其他Segment锁。
- put操作造成的扩容时,不是对整个容器进行双倍扩容,而只对某个segment进行双倍扩容,节省了大量空间。
- size操作:先尝试2次通过不锁住Segment的方式来统计各个Segment大小,如果统计的过程中,容器的count发生了变化,则再采用加锁的方式来统计所有Segment的大小。
- 链接:Java并发46:并发集合系列-基于锁分段技术的ConcurrentHashMap
(4)Deque 双端队列
- ConcurrentLinkedDeque :
- 基于非阻塞CAS算法的非阻塞双向无界列表。
- 算法与ConcurrentLinkedQueue类似。
- 不仅支持FIFO操作,而且也支持FILO操作。
- 链接:Java并发48:并发集合系列-基于CAS算法的非阻塞双向无界队列ConcurrentLinkedDueue
- LinkedBlockingDeque :
- 基于独占锁ReenTratLock+链表的阻塞双向无界队列。
- 有两个ReentrantLock的独占锁,分别用来控制元素入队加锁和出队加锁。
- 不仅支持FIFO操作,而且也支持FILO操作。
- size()方法和remove()方法在并发环境中较为精确。
- 实现原来:LinkedBlockingQueue 类似。
- 如何实现线程安全:volatile变量 + ReenTrantLock独占锁。
- 链接:Java并发50:并发集合系列-基于独占锁实现的双向阻塞队列LinkedBlockingDeque
(5)Queue 队列
- ConcurrentLinkedQueue :
- 基于非阻塞CAS算法的非阻塞单向无界列表。
- 只支持FIFO操作。
- size()方法和contains()方法在并发环境中并不精确。
- 实际应用:Tomcat中NioEndPoint。
- 如何实现线程安全:volatile变量(head,tail)保证可见性,CAS操作保证原子性和有序性。
- 链接:Java并发47:并发集合系列-基于CAS算法的非阻塞单向无界列表ConcurrentLinkedQueue
- LinkedBlockingQueue :
- 基于独占锁ReenTratLock+链表的阻塞单向无界队列。
- 有两个ReentrantLock的独占锁,分别用来控制元素入队加锁和出队加锁。
- 只支持FIFO操作。
- size()方法和remove()方法在并发环境中较为精确。
- 实际应用:tomcat中任务队列TaskQueue。
- 如何实现线程安全:volatile变量 + ReenTrantLock独占锁。
- 链接:Java并发49:并发集合系列-基于独占锁实现的单向阻塞队列LinkedBlockingQueue
- ArrayBlockingQueue :
- 基于独占锁ReenTratLock+数组的阻塞单向有界队列。
- 只有一个全局ReentrantLock的独占锁,用来控制元素入队加锁和出队加锁。
- 只支持FIFO操作。
- size()方法和remove()方法在并发环境中较为精确。
- 如何实现线程安全:ReenTrantLock全局独占锁。
- 链接:Java并发51:并发集合系列-基于独占锁+数组实现的单向阻塞有界队列ArrayBlockingQueue
- PriorityBlockingQueue :
- 基于独占锁ReenTratLock+二叉树最小堆的阻塞单向无界优先级队列。
- 只有一个全局ReentrantLock的独占锁,用来控制元素入队加锁和出队加锁。
- size()方法在并发环境中精确的。
- 如何实现线程安全:ReenTrantLock全局独占锁。
- 链接:Java并发52:并发集合系列-基于独占锁+二叉树最小堆实现的单向阻塞无界优先级队列PriorityBlockingQueue
- DelayQueue :
- 基于独占锁ReenTratLock+优先级阻塞队列PriorityBlockingQueue的阻塞单向无界延时队列。
- 最先过期的元素具有最高优先级。
- 只有一个全局ReentrantLock的独占锁,用来控制元素入队加锁和出队加锁。
- 如何实现线程安全:ReenTrantLock全局独占锁。
- 主要用途:缓存队列和超时任务处理。
- 链接:Java并发53:并发集合系列-基于独占锁+PriorityBlockingQueue实现的单向阻塞无界延时队列DelayQueue
- SynchronousQueue :
- 基于CAS操作的非阻塞无数据缓冲队列。
- 不存在数据空间:队列头元素是第一个排队要插入数据的线程,而不是要交换的数据。
- 因为没有数据空间,很多方法不可用:peek、clear、contains、isEmpty、size、remove等等。
- 应用场景:生产者的线程和消费者的线程同步以传递某些信息、事件或者任务。
- 应用实例:Executors.newCachedThreadPool()。
- 可以这样来理解:生产者和消费者互相等待对方,握手,然后一起离开。
- 链接:Java并发54:并发集合系列-基于CAS算法的非阻塞无数据缓冲队列SynchronousQueue
- LinkedTransferQueue :
- 基于预占模式+链表的单向阻塞无界队列。
- 预占模式:有就拿货走人,没有就占个位置等着,等到或超时。
- transfer算法采用所谓双重数据结构(dual data structures):保留与完成。
- 只支持FIFO操作。
- 应用实例:生产者与消费者。
- 链接:Java并发55:并发集合系列-基于预占模式+链表的单向阻塞无界队列LinkedTransferQueue