ConcurrentHashmap、CopyOnWriteArrayList、BlockingQueue线程安全集合

概要

jdk5引入 java.util.concurrent.*下的线程安全集合,里面包含关键字:BlockingQueue、CopyOnWrite、Concurrent;

  • Blocking大部分实现基于锁,并提供等待性方法;
  • CopyOnWrite 之类容器修改开销相对较重;
  • Concurrent类型的容器:
    • 基于lock-free 再常见的多线程访问场景,一般可以提供较高吞吐量;
    • 往往提供了较低的遍历一致性,也就说当迭代器遍历时,修改了容器中的元素,而迭代器继续遍历;
    • 读取的性能具有一定的不确定性;

线程安全控制的三个级别

  • JVM级别:通常以CAS指令形式,是一种低级别的、细粒度的技术;
  • 低级使用程序类——锁定和原子类,使用CSA作为并发原语,ReentrantLock类提供与synchronized原语相同的所动和内存语义;
  • 高级使用程序类:信号、互斥、屏障、交换程序等;
  1. 线程安全集合
  • 遗留的线程安全集合,如Hashtable
  • Conllection 里的一系列以synchronized开头的方法,可以把非线程安全的集合包装成线程安全的集合;
    • 体现了设计模式中的装饰器模式;
  • juc下的线程安全集合
    • CopyOnWrite 开头的集合,采用了写入时拷贝的思想,来提高并发度;
    • Concurrent 支持并发(线程安全的集合);
    • Blocking支持阻塞操作;
  1. CopyOnWriteArrayList->Vector
    支持多线程并发读取,支持单线程写入;

读取方法不加锁,进行写入操作时,将原有数组复制一份,然后修改操作在新数组,等修改操作完了之后,将新数组替换旧数组;
把读写操作分开,读不加锁,写加锁,用空间来换取了读不加锁;

出现该集合的原因时由于Concurrent下的类的弱一致性迭代器,也就是当使用Concurrent下的集合返回的迭代器进行遍历迭代时,如果元素被修改则会抛出ConcurrentModificationException异常,且这个异常是不可控的。除此之外还有CopyWriteArraySet;

  1. ConcurrentHashMap->Hashtable

Hashtable是锁住了整个map集合,而ConcurrentHashMap 只会锁住map集合中的一个桶
1.7 之前
数组(Segment)+数组(hashEntry)+链表(HashEntry)
分段锁

  • 首先是一个segment的数组,每个segment的元素又是一个Node数组,Node数组里又存放着链表;
  • 锁定以segment为单位;
  • 初始化,不是懒惰初始化,先把16个segment创建好,这个容量指定后就不能改变了;
  • put 先找到segment,调用segment.put
    • 懒惰初始化HashEntry数组(除了segment[0]除外);
    • segment本身实现了可入锁,在put操作时会用lock加锁
    • 元素添加至链表头;
  • get 无锁操作,仅需要保证可见性,扩容过程中,get先发生就从旧表中读取,get发生后就从新表中读取;
  • 扩容 发生在put方法内,因此是提前加了锁的;
  • size 计算元素个数前,先不加锁多次计算,前后两次结果如一样,认为个数正确,计算超过3次将所有segment锁住,重新计算个数返回;

在这里插入图片描述
1.8 中
Hashtable 是锁住了整个map集合,而ConcurrentHashmap只会锁住map集合的一个桶,根据桶的多少,可以进一步提高并发度,只要读写操作落在不同的桶里,操作就可以并行执行;

  • 数组(Node) + 链表/红黑树
  • 初始化数组时,使用cas来保证并发安全性,懒惰初始化;
  • 当容量小于64首先尝试进行扩容,当超过这个容量并且链表大于8,会将链表树化,树化过程中会锁住链表头;
  • put 操作会锁住链表头,新加的元素放入链表尾部;
  • get 操作不需要加锁,仅需要用cas保证元素的可见性;
  • 扩容以链表为单位扩容,当扩容时有多个线程来同时访问,这些线程会协助扩容;
  • size 元素个数保存在baseCount中,并发时的个数变动保存在CounterCell[] ,最后统计数量累加即可;
  1. ConcurrentSkipListMap
    类似与之前的LinkedHashMap 都可以保证元素遍历顺序和放入的顺序是一致的;
    但是LinkedHashMap是非线程安全,而ConcurrentSkipListMap是线程安全的;

数据结构为 跳表
在这里插入图片描述

上层白色的链表起到快速定位底层链表的作用;
以插入元素为例:
在这里插入图片描述

5. BlockingQueue
经常用来实现生产消费模式,来解耦生产者、消费者线程;

  • 队列的选用:
    • ArrayBlockingQueue有明确的容量限制,LinkedBlockingQueue取决于在创建时是否指定,SynchronousQueue则干脆不能缓存任何元素;
    • ArrayBlockingQueue在空间利用地上比LinkedBlockingQueue紧凑;

猜你喜欢

转载自blog.csdn.net/mashaokang1314/article/details/88050186