java 容器_Java并发 -- 并发容器

同步容器

  1. Java 1.5之前提供的同步容器虽然也能保证线程安全,但性能很差
  2. Java中的容器主要分为四大类,分别为List、Map、Set和Queue,并不是所有的Java容器都是线程安全的
  3. 非线程安全的容器变成线程安全的容器的简单方案:synchronized把非线程安全的容器封装在对象内部,然后控制好访问路径即可

线程安全的ArrayList

复制

public class SafeArrayList {    private List list = new ArrayList<>();    public synchronized T get(int idx) {        return list.get(idx);    }    public synchronized void add(int idx, T t) {        list.add(idx, t);    }    public synchronized boolean addIfNotExist(T t) {        if (!list.contains(t)) {            list.add(t);            return true;        }        return false;    }}

Collections.synchronized

复制

Collections.synchronizedList(new ArrayList());Collections.synchronizedSet(new HashSet());Collections.synchronizedMap(new HashMap());

组合操作存在竟态条件问题

  1. 上面的addIfNotExist就包含组合操作
  2. 组合操作往往隐藏着竟态条件问题,即便每个操作都能保证原子性,也不能保证组合操作的原子性
  3. 用迭代器遍历同步容器也存在竟态条件问题,因为组合操作不具备原子性

复制

// 存在竟态条件问题List list = Collections.synchronizedList(new ArrayList<>());Iterator iterator = list.iterator();while (iterator.hasNext()) {    process(iterator.next());}// 并发安全,先锁住list再执行遍历操作List list = Collections.synchronizedList(new ArrayList<>());synchronized (list) {    Iterator iterator = list.iterator();    while (iterator.hasNext()) {        process(iterator.next());    }}

并发容器

  1. Java在1.5之前所谓的线程安全容器,主要指的是同步容器
  2. 同步容器最大的问题是性能差,所有方法都用synchronized来保证互斥,串行度太高
  3. 在Java 1.5提供了性能更高的容器,称为并发容器

分类

并发容器数量众多,但依旧可以分成四大类:List、Map、Set和Queue

acdbcf54b449c24c166fa143e3f58b9c.png

List

  1. List里面只有一个实现类就是CopyOnWriteArrayList
  2. CopyOnWrite即在执行写操作的时候会将共享变量重新复制一份出来,这样的好处是读操作完全无锁
  3. CopyOnWriteArrayList内部维护一个数组,成员变量array指向这个内部数组,所有的读操作都是基于array进行的
  4. 如果在遍历array的同时,还有一个写操作会将array复制一份,然后在新复制的数组上执行写操作,执行完之后再将array指向这个新的数组
  5. 因此读写是并行的, 遍历操作一直都是基于原array执行的,而写操作则是基于新array执行的
  6. 应用场景:仅适用于写操作非常少的场景,而且能够容忍读写的短暂不一致
  7. CopyOnWriteArrayList的迭代器是只读的,不支持增删改,因为对快照进行增删改是没有意义的
d7e1426d1c4130ea8be8f051383a607a.png
2b2656c735bebcc9d763fa769db1e9c5.png

Map

  1. Map接口的两个实现:ConcurrentHashMap和ConcurrentSkipListMap
  2. ConcurrentHashMap的key是无序的,而ConcurrentSkipListMap的key是有序
  3. ConcurrentSkipListMap里面的SkipList本身是一种数据结构,翻译成跳表跳表执行插入、删除和查询操作的平均复杂度为O(log n)理论上与并发线程数无关,适用于并发度非常高的情况(ConcurrentHashMap的性能也不能满足要求)

集合类KeyValue线程安全HashMap允许为null允许为null否TreeMap不允许为null允许为null否HashTable不允许为null不允许为null是ConcurrentHashMap不允许为null不允许为null是ConcurrentSkipListMap不允许为null不允许为null是

Set

  1. Set接口的两个实现:CopyOnWriteArraySet和ConcurrentSkipListSet
  2. 原理与CopyOnWriteArrayList和ConcurrentSkipListMap类似

Queue

  1. JUC中的Queue类的并发容器是最复杂的,可以从两个维度分类,阻塞/非阻塞单端/双端
  2. 阻塞/非阻塞:阻塞指的是当队列已满时,入队操作阻塞;当队列已空时,出队操作阻塞
  3. 单端/双端:单端指的是只能队尾入队,队首出队;双端指的是队首队尾皆可出队入队
  4. 在JUC中,阻塞队列用Blocking关键字标识,单端队列用Queue标识,双端队列用Qeque标识

单端阻塞队列

  1. 其实现包括ArrayBlockingQueueLinkedBlockingQueueSynchronousQueueLinkedTransferQueuePriorityBlockingQueueDelayQueue
  2. 内部一般都会持有一个队列该队列可以是数组(ArrayBlockingQueue)也可以是链表(LinkedBlockingQueue)甚至不持有队列(SynchronousQueue),生产者线程的入队操作必须等待消费者线程都出队操作
  3. LinkedTransferQueue融合了LinkedBlockingQueue和SynchronousQueue的功能,性能比LinkedBlockingQueue更好
  4. PriorityBlockingQueue支持按优先级出队
  5. DelayQueue支持延时队列
6fc5a34e0baa5325643aa050939eea8d.png

双端阻塞队列

其实现是LinkedBlockingDeque

0f0c70acdf2fca16f51e3bf479d2ec35.png

单端非阻塞队列

其实现是ConcurrentLinkedQueue

双端非阻塞队列

其实现是ConcurrentLinkedDeque

是否有界

  1. 使用队列时,要格外注意队列是否支持有界
  2. 实际工作中,一般不建议使用无界的队列,因为有可能会导致OOM
  3. 上面提到的Queue,只有ArrayBlockingQueueLinkedBlockingQueue是支持有界的

来源:http://zhongmingmao.me/2019/05/12/java-concurrent-concurrent-container/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值