由于私人原因,暂时没有太多时间用于并发集合类型的实例学习上面。
所以从本章开始,后续并发集合类型相关文章都是转载文章,特此说明。
这些转载文章的叙述角度各不相同,不过不影响我们通过这些文章对并发集合有一个初步的理解。
集合
编程,离不开数据结构。
JDK提供了Java集合框架(Java Collections framework),它包括可以用来实现多种不同的数据结构的接口、类和算法,如HaspMap、ArrayList等等。
我们在使用集合框架的时候,需要十分小心以保证其多线程的安全性,因为大多数集合类并没有对并发访问进行控制。
并发集合
为了解决这些集合框架造成的安全性问题,JDK逐渐提供了越来越多的并发集合类型。
我们在并发环境中,使用这些并发集合,不会产生数据不一致的问题。
阻塞与非阻塞
JDK提供我们的并发集合类型,按照阻塞方式分为两种:
阻塞队列
包含添加操作:如果不能立即进行添加,则是因为集合已满;执行该操作的线程将被阻塞,直到添加成功。
包含删除操作:如果不能立即进行删除,则是因为集合已空;执行该操作的线程将被阻塞,直到删除成功。
非阻塞队列
包含添加操作:如果不能立即进行添加,则将返回null值或抛出异常。
包含删除操作:如果不能立即进行删除,则将返回null值或抛出异常。
集合类型
JDK提供我们的并发集合类型,按照集合类型分为以下五种:
List 列表
CopyOnWriteArrayList :
基于写时复制(Copy-On-Write)思想实现。
适用于读多写少的并发场景,如黑名单,白名单等等。
并发读的性能高;并发写的空间消耗大。
只能保证数据的最终一致性,而非实时一致性。
读不会阻塞,写会阻塞。
链接:Java并发44:并发集合系列-基于写时复制的CopyOnWriteArrayList和CopyOnWriteArraySet
Set 集合
CopyOnWriteArraySet :
基于写时复制(Copy-On-Write)思想实现。
适用于读多写少的并发场景,如黑名单,白名单等等。
并发读的性能高;并发写的空间消耗大。
只能保证数据的最终一致性,而非实时一致性。
读不会阻塞,写会阻塞。
链接:Java并发44:并发集合系列-基于写时复制的CopyOnWriteArrayList和CopyOnWriteArraySet
ConcurrentSkipListSet :
基于跳表(SkipList)实现。
跳表(SkipList)可以用来替代红黑树,使用概率均衡技术,插入、删除操作更简单、更快。
跳表(SkipList)本质上是以空间换取时间。
ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的。
ConcurrentSkipListMap中的元素是key-value键值对,而ConcurrentSkipListSet只用到了前者中的key。
链接:Java并发45:并发集合系列-基于跳表的ConcurrentSkipListSet和ConcurrentSkipListMap
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
Deque 双端队列
ConcurrentLinkedDeque :
基于非阻塞CAS算法的非阻塞双向*列表。
算法与ConcurrentLinkedQueue类似。
不仅支持FIFO操作,而且也支持FILO操作。
链接:Java并发48:并发集合系列-基于CAS算法的非阻塞双向*队列ConcurrentLinkedDueue
LinkedBlockingDeque :
基于独占锁ReenTratLock+链表的阻塞双向*队列。
有两个ReentrantLock的独占锁,分别用来控制元素入队加锁和出队加锁。
不仅支持FIFO操作,而且也支持FILO操作。
size()方法和remove()方法在并发环境中较为精确。
实现原来:LinkedBlockingQueue 类似。
如何实现线程安全:volatile变量 + ReenTrantLock独占锁。
链接:Java并发50:并发集合系列-基于独占锁实现的双向阻塞队列LinkedBlockingDeque
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