并发容器类

一、并发容器概览

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

二、ConcurrentHashMap

1.Map简介

Map的实现:
在这里插入图片描述

2.为什么HashMap不安全

  1. 同时put碰撞导致数据丢失;
  2. 同时put扩容导致数据丢失;
  3. 会形成死循环:JDK1.7中,扩容采取头插法,而且通过重新计算哈希值来计算新的地址。这样在扩容时就可能会形成环。

3.ConcurrentHashMap分析

(1)JDK1.7中

在这里插入图片描述
1.7中的ConcurrentHashMap最外层是多个segment每个segment的底层数据结构与HashMap类似,仍然是数组和链表组成的拉链法。

每个segment独立上ReentrantLock锁,每个segment之间互不影响,提高了并发效率。

默认有16个segment,所以最多有16个线程并发写。这个默认值可以在初始化时指定其他值,但是初始化之后就不可扩容了。

(2)JDK1.8中

不使用segment,而是node。使用synchronized+CAS保证并发安全。

结构与1.8的HashMap类似。

putVal()源码分析
if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }

从这一段代码可以看到,如果通过哈希值判断对应下标位置是空的,就用casTabAt方法将其插入。而casTabAt用的正是CAS的方法。

synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }

这一段是向链表中插入数据的代码,可以看到使用synchronized包裹来保证并发安全。

(3)为什么7到8要修改结构
  1. 提高了并发性
  2. 链表 --> 链表/红黑树,效率更高

三、CopyOnWriteArrayList

1.简介

CopyOnWriteArrayList是线程安全的ArrayList,用来代替Vector和SynchronizedList。

使用场景

  • 读操作可以尽可能地快,而写即使慢一些也没有太大关系。(商品列表、黑名单)

CopyOnWriteArrayList的读写规则

  • 读取完全不加锁
  • 写入时不会阻塞读取操作(和读写锁不同)。只有写入和写入之间需要同步。

CopyOnWriteArrayList可以在迭代过程中修改,ArrayList不能在迭代过程中修改。尽管可以修改,但是在迭代过程中读取的还是修改之前的内容。

2.实现原理

在写的时候,复制一份出来。修改复制出来的那部分,修改完毕后将原来的引用指向新的部分。

所以,新旧互不干涉,在读取的时候还是读的旧的部分。

3.缺点

  1. 数据一致性问题:只能保证数据的最终一致性,不能保证数据的实时一致性。
  2. 内存占用问题:进行写操作时,内存中会同时驻扎两个对象内存。

4.源码分析

  • 和ArrayList一样,底层采用数组实现。
  • 保证线程安全的方式是ReentrantLock

四、并发队列

1.为什么用

使用队列就可以在线程间传递数据:如生产者消费者问题;

如果队列是并发安全的,那么直接就可以用了,而不用去考虑线程安全问题。

2.关系

在这里插入图片描述

3.阻塞队列★

(1)定义

阻塞队列是具有阻塞功能的队列。通常阻塞队列一端是给生产者放数据用,另一端消费者拿数据用。阻塞队列是线程安全的。

(2)方法
  • take():取数据,队列为空时阻塞
  • put():放数据,队列满时阻塞
  • add():放数据,如果满了会抛出异常
  • remove():取数据,空了会抛出异常
  • element():查看头部,空了会抛出异常
  • offer():放数据,满了返回false
  • poll():取数据,为空会返回null
  • peek():查看队首,为空返回null
(3)实现
ArrayBlockingQueue

有界,可以指定公平还是不公平。

使用数组实现,用ReentrantLock保证线程安全。

LinkedBlockingQueue

是无界的。

使用链表作为底层数据结构。Node存放数据,有两把ReentrantLock(put、take)。

PriorityBlockingQueue

线程安全的PriorityQueue,也是无界的。

SynchronizedQueue

容量为0,直接交换元素,不存储

DelayQueue

延迟队列,根据延迟时间排序。所有元素必须实现Delayed接口。

4.非阻塞队列

只有ConcurrentLinkedQueue这一种实现,其使用CAS来实现线程安全。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值