• 第十章 五种并发包总结


    1、常用的五种并发包

    • ConcurrentHashMap
    • CopyOnWriteArrayList
    • CopyOnWriteArraySet
    • ArrayBlockingQueue
    • LinkedBlockingQueue

    2、ConcurrentHashMap

    • 线程安全的HashMap的实现
    • 数据结构:一个指定个数的Segment数组,数组中的每一个元素Segment相当于一个HashTable(一个HashEntry[])
    • 扩容的话,只需要扩自己的Segment而非整个table扩容
    • key与value均不可以为null,而hashMap可以
    • 向map添加元素
      • 根据key获取key.hashCode的hash值
      • 根据hash值算出将要插入的Segment
      • 根据hash值与Segment中的HashEntry的容量-1按位与获取将要插入的HashEntry的index
      • 若HashEntry[index]中的HashEntry链表有与插入元素相同的key和hash值,根据onlyIfAbsent决定是否替换旧值
      • 若没有相同的key和hash,直接返回将新节点插入链头,原来的头节点设为新节点的next(采用的方式与HashMap一致,都是HashEntry替换的方法)
    • ConcurrentHashMap基于concurrencyLevel划分出多个Segment来存储key-value,这样的话put的时候只锁住当前的Segment,可以避免put的时候锁住整个map,从而减少了并发时的阻塞现象
    • 从map中获取元素
      • 根据key获取key.hashCode的hash值
      • 根据hash值与找到相应的Segment
      • 根据hash值与Segment中的HashEntry的容量-1按位与获取HashEntry的index
      • 遍历整个HashEntry[index]链表,找出hash和key与给定参数相等的HashEntry,例如e
        • 如没找到e,返回null
        • 如找到e,获取e.value
          • 如果e.value!=null,直接返回
          • 如果e.value==null,则先加锁,等并发的put操作将value设置成功后,再返回value值
    • 对于get操作而言,基本没有锁,只有当找到了e且e.value等于null,有可能是当下的这个HashEntry刚刚被创建,value属性还没有设置成功,这时候我们读到是该HashEntry的value的默认值null,所以这里加锁,等待put结束后,返回value值
    • 加锁情况(分段锁)
      • put
      • get中找到了hash与key都与指定参数相同的HashEntry,但是value==null的情况
      • remove
      • size():三次尝试后,还未成功,遍历所有Segment,分别加锁(即建立全局锁)

    3、CopyOnWriteArrayList

    • 线程安全且在读操作时无锁的ArrayList
    • 采用的模式就是"CopyOnWrite"(即写操作-->包括增加、删除,使用复制完成)
    • 底层数据结构是一个Object[],初始容量为0,之后每增加一个元素,容量+1,数组复制一遍
    • 遍历的只是全局数组的一个副本,即使全局数组发生了增删改变化,副本也不会变化,所以不会发生并发异常。但是,可能在遍历的过程中读到一些刚刚被删除的对象
    • 增删改上锁、读不上锁
    • 读多写少且脏数据影响不大的并发情况下,选择CopyOnWriteArrayList

    4、CopyOnWriteArraySet

    • 基于CopyOnWriteArrayList,不添加重复元素

    5、ArrayBlockingQueue

    • 基于数组、先进先出、线程安全,可实现指定时间的阻塞读写,并且容量可以限制
    • 组成:一个对象数组+1把锁ReentrantLock+2个条件Condition
    • 三种入队对比
      • offer(E e):如果队列没满,立即返回true; 如果队列满了,立即返回false-->不阻塞
      • put(E e):如果队列满了,一直阻塞,直到数组不满了或者线程被中断-->阻塞
      • offer(E e, long timeout, TimeUnit unit):在队尾插入一个元素,,如果数组已满,则进入等待,直到出现以下三种情况:-->阻塞
        • 被唤醒
        • 等待时间超时
        • 当前线程被中断
    • 三种出对对比
      • poll():如果没有元素,直接返回null;如果有元素,出队
      • take():如果队列空了,一直阻塞,直到数组不为空或者线程被中断-->阻塞
      • poll(long timeout, TimeUnit unit):如果数组不空,出队;如果数组已空且已经超时,返回null;如果数组已空且时间未超时,则进入等待,直到出现以下三种情况:
        • 被唤醒
        • 等待时间超时
        • 当前线程被中断
    • 需要注意的是,数组是一个必须指定长度的数组,在整个过程中,数组的长度不变,队头随着出入队操作一直循环后移
    • 锁的形式有公平与非公平两种
    • 在只有入队高并发或出队高并发的情况下,因为操作数组,且不需要扩容,性能很高

    6、LinkedBlockingQueue

    • 基于链表实现,读写各用一把锁,在高并发读写操作都多的情况下,性能优于ArrayBlockingQueue
    • 组成一个链表+两把锁+两个条件
    • 默认容量为整数最大值,可以看做没有容量限制
    • 三种入队与三种出队与上边完全一样,只是由于LinkedBlockingQueue的的容量无限,在入队过程中,没有阻塞等待
  • 相关阅读:
    (转)3D模板阴影原理
    (转)c++模版:包含模型、显式实例化、分离模型
    (转)C++内存管理
    (转)Ogre 安装 配置 问题
    (转)详细解说hash_map
    (转)STL中map用法详解
    VB Format函数
    转 python内置正则表达式(re)模块官方文档简要中文版
    《编程的奥秘》读后感
    python基础(5)正则表达式
  • 原文地址:https://www.cnblogs.com/java-zhao/p/5139895.html
Copyright © 2020-2023  润新知