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

 

posted @ 2021-10-12 19:49  姚春辉  阅读(186)  评论(0编辑  收藏  举报