java中的并发容器

  • Post author:
  • Post category:java


一、并发容器概览

  • ConcurrentHashMap:线程安全的HashMap
  • CopyOnWriteArrayList:线程安全的List
  • BlockingQueue:这是一个接口,表示阻塞队列,非常适合用于作为数据共享的通道
  • ConcurrentLinkedQueue:高效的非阻塞并发队列,使用链表实现。可以看做一个线程安全的LinkedList。
  • ConcurrentSkipList:是一个Map。使用跳表的数据结构进行快速查找。

二、ConcurrentHashMap

数据结构和hashMap的数据结构类似


为什么HashMap是线程不安全的

  • 同时put碰撞导致数据丢失
  • 同时put扩容导致数据丢失
  • 死循环造成的CPU100%(只存在jdk7):多个线程在同时扩容的时候会造成链表的死循环


HashMap关于并发的特点

  • 非线程安全
  • 迭代时不允许修改内容
  • 只读的并发是安全的


ConcurrentHashMap 的 putVAl流程

  1. 判断 key value 不为空
  2. 计算hash值
  3. 根据对应位置节点的类型来赋值,或者helpTransfer,或者增长链表,或者给红黑树增加节点
  4. 检查满足阈值就“红黑树化”
  5. 返回oldVal


ConcurrentHashMap 的 get 流程

  1. 计算hash值
  2. 找到对应的位置,根据情况进行:直接取值、红黑树里找值、遍历链表取值
  3. 返回找到的结果


为什么数据结构中超过8转为红黑树

  1. 因为红黑树每个节点所占用的空间是链表的两倍,所以最开始用占用空间较少的链表
  2. 因为链表长度为8时发生hash碰撞的概率为千万之一,如果真的有这种情况转为红黑数依然可以保证的查询效率

三、CopyOnWriteArrayList

  • CopyOnWriteArrayList 代替Vector和SynchronizedList
  • Vector和SynchronizedList的锁的粒度太大,并发效率相对比较低,并且迭代时无法编辑
  • CopyOnWriteArraySet代替Set


适用场景

读多写少:读操作可以尽可能地块,而写慢一些没有太大关系(例如:黑名单,每日更新)


读写规则

读取完全不需要加锁,写入也不会阻塞读取操作,只要写入和写入之间需要进行同步等待


实现原理

  • CopyOnWrite的含义:在写的时候copy一份新的副本出来,对副本进行写,写操作完成后将引用指向副本。
  • 创建新副本,读写分离
  • “不可变”原理
  • 迭代的时候如果对数组的内容进行修改,但是迭代不感知依旧是旧的数据


缺点

  • 数据一致性问题:CopyOnWrite容易只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果希望写入的数据马上就能读到不要使用copyOnWrite容器
  • 内存占用问题:因为CopyOnWrite的写是复制机制,所以在进行写操作的时候内存会同时驻扎两个对象的内存。

四、阻塞队列


各并发队列的关系图


什么是阻塞队列

  • 阻塞队列是具有阻塞功能的队列,所以首先是一个队列其次是具有阻塞功能
  • 通常阻塞队列一端是给生产者放数据用,另一端给消费者拿数据用。阻塞队列是线程安全的,所以生产者和消费者可以是多线程的,如下图所示

  • 是否有界(容量有多大):这是一个非常重要的属性,无界队列意味着里面可以容纳非常多(Integer.MAX_VALUE,约为2的31,可以近似认为是无限容量)
  • 阻塞队列和线程池的关系:阻塞队列是线程池的重要组成部分


BlockingQueue带有阻塞功能的方法

  • take()方法:获取并移除的头结点,一旦如果执行take的时候,队列里无数据则阻塞,直到队列有数据,如下图所示

  • put()方法:插入元素。但是如果队列已满那么就无法继续插入则阻塞直到队列里面有了空闲空间,如下图所示


ArrayBlockingQueue

  1. 有界
  2. 指定容量
  3. 公平:可以指定是否需要保证公平,如果想保证公平那么等待了最长时间的线程会被优先处理,不过这会同时带来一定的性能损耗


LinkedBlockingQueue

  1. 无界
  2. 容量 Integer.MAX_VALUE
  3. 内部结构:Node、两把锁。


PriorityBlockingQueue

  1. 支持优先级
  2. 自然顺序(而不是先进先出)
  3. 无界队列:当队列容量不够会进行扩容
  4. PriorityQueue的线程安全版本


SynchronousQueue

  1. 容量为0
  2. 需要注意的是,SynchronousQueue的容量不是1而是0,因为SynchronousQueue不需要去持有元素,而是直接传递
  3. 效率高
  4. SynchronousQueue没有peek等函数,因为peek的含义是取出头结点,但是 SynchronousQueue 的容量为0,所以连头结点都没有,也没有peek方法,同理也没有iterate相关方法
  5. 是一个极好的用来直接传递的并发数据结构
  6. SynchronousQueue是线程池 newCachedThredPool使用的阻塞队列


DelayQueue

  • 延迟队列:根据延迟时间排序
  • 无界队列
  • 元素需要实现Delayed接口,规定排序规则

五、非阻塞队列

并发中的非阻塞对列只有ConcurrentLinkedQueue 这一种,ConcurrentLinkedQueue使用链表作为其数据结构,使用CAS非阻塞算法来实现线程安全(不具备阻塞功能),适合用在对性能要求较高的并发场景。

六、如何选择适合自己的队列

  • 边界
  • 空间
  • 吞吐量



版权声明:本文为weixin_39477597原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。