
ConcurrentHashMap
变化
JDK 1.7

1)包含一个 Segment 数组,一个 Segment 段包含一个 HashEntry 数组,每一个HashEntry节点相当于一个链表
2)每个 Segment 段拥有一个独立的 ReentrantLock 可重入锁。读取操作不需要锁,写入操作仅锁定相关的段,减小了锁冲突概率
3)对于一次写操作要进行两次哈希操作,第一次哈希到 segment 数组,第二次哈希到HashEntry数组
JDK 1.8

1)取消了 Segment 分段锁,整个容器只维护一个 Node 数组,每个位置都相当于一个 HashMap
2)锁只在 Node 节点级别上进行。插入时先用 CAS 操作尝试插入,冲突了再用 synchronized 只锁住链表或树的头节点
get 方法线程安全
通过 volatile 关键字,确保数据的可见性
不支持null
避免混淆:会产生混淆,例如使用get(key)返回值为 null 时,无法区分是 key 不存在,还是 value 值是 null
简化实现:不需要额外的逻辑去区分返回 null ,是因为 key 不存在还是 value 为 null
ConcurrentLinkedQueue
概念
ConcurrentLinkedQueue是一个基于链表、无界的、线程安全的、非阻塞队列
1)无界:理论上只要内存足够,它可以一直添加元素,没有容量限制
2)线程安全、非阻塞:通过 CAS 操作,线程在入队/出队时不会被挂起等待
3)数据结构:内部是一个单向链表,有两个分别指向头和尾的volatile引用:head 和tail
4)内部不允许为null
“松弛”更新策略
策略:
1)head和tail指针,不一定精确指向首尾节点,可能滞后数个节点
2)当执行入队/出队操作时,线程会从当前指针位置向后遍历,直到找到真正的边界节点。如果发现指针明显滞后于实际边界,会尝试用 CAS 原子地更新指针
3)由于多线程竞争,这种更新不一定总能成功,但算法的正确性不依赖于每次更新都必须成功
优点:
尽管增加了一点遍历成本,但极大得减少了 CAS 竞争
阻塞队列 BlockingQueue
ArrayBlockingQueue
基于数组的有界阻塞队列
1)构造时大小确定,不能更改。这个界限提供了流量控制,有助于资源的合理使用
2)队列为空进行取操作或队列满进行插入操作都会阻塞
3)使用一个单独的 ReentrantLock 来控制对队列的访问
4)默认是非公平的(构造函数传入true后即是公平的)
LinkedBlockingQueue
基于链表的线程安全的阻塞队列:
1)可以在构造时指定最大容量。如果不指定,默认为Integer.MAX_VALUE
2)队列为空进行取操作或队列满进行插入操作都会阻塞
3)使用两个锁(putLock和takeLock),一个用于放入操作,另一个用于取出操作。锁分离,很适合生产和消费频率差不多的场景
PriorityBlockingQueue
具有优先级排序特性的无界阻塞队列
可以通过实现 Comparable 接口来定义自然排序
SynchronousQueue
一个非常特殊的阻塞队列,它不存储任何元素,允许线程直接将元素交付给另一个线程
若一个线程尝试插入一个元素,并且有另一个线程尝试移除一个元素,则插入和移除操作将同时成功
LinkedTransferQueue
一个基于链表结构的无界传输队列,实现了 TransferQueue 接口,它提供了一种强大的线程间交流机制。
除了基础的阻塞队里功能外,还包括“转移”语义:如果消费者已经在等待,允许一个元素直接从生产者传输给消费者。反之,元素将入队
常用方法:
transfer(E e):将元素转移到等待的消费者,如果不存在等待的消费者,则元素会入队并阻塞直到被消费
tryTransfer(E e):尝试立即转移元素,如果有消费者正在等待,则传输成功;否则,返回 false
LinkedBlockingDeque
链表结构的阻塞 Deque
DelayQueue
一个无界阻塞队列,用于存放实现了 Delayed 接口的元素,这些元素只能在其到期时才能从队列中取走
可以实现基于时间优先级的调度服务
CopyOnWrite容器
概念
1)向容器添加元素时,先复制出一个新的容器,向新的容器添加元素,再将原容器的引用指向新的容器
2)多个线程在读的时候,不需要加锁,读取的是旧容器,这牺牲了数据的实时性
原理
1)底层数组使用volatile声明,根据 Java 内存模型的 happens-before 原则:对 volatile 变量的写操作,对后续所有线程对该变量的读操作可见
2)写操作使用synchronized保证复制-修改-替换整个过程是原子的
