关于java:并发编程不关注并发容器

4次阅读

共计 2845 个字符,预计需要花费 8 分钟才能阅读完成。

同步容器及其注意事项

Java 中的容器次要能够分为四个大类,别离是 List、Map、Set 和 Queue,但并不是所有的 Java 容器都是线程平安的。例如,咱们罕用的 ArrayList、HashMap 就不是线程平安的。

在介绍线程平安的容器之前,咱们先思考这样一个问题:如何将非线程平安的容器变成线程平安的容器?

实现思路其实很简略,只有把非线程平安的容器封装在对象外部,而后管制好拜访门路就能够了。

Java SDK 的开发人员在 Collections 这个类中提供了一套齐备的包装类,比方上面的示例代码中,别离把 ArrayList、HashSet 和 HashMap 包装成了线程平安的 List、Set 和 Map。

List list = Collections.synchronizedList(new ArrayList());
Set set = Collections.synchronizedSet(new HashSet());
Map map = Collections.synchronizedMap(new HashMap());

组合操作须要留神竞态条件问题。即使每个操作都能保障原子性,也并不能保障组合操作的原子性,这个肯定要留神。

下面咱们提到的这些通过包装后线程平安容器,都是基于 synchronized 这个同步关键字实现的,所以也被称为同步容器。

Java 提供的同步容器还有 Vector、Stack 和 Hashtable,这三个容器不是基于包装类实现的,但同样是基于 synchronized 实现的,对这三个容器的遍历,同样要加锁保障互斥。

并发容器及其注意事项

Java 在 1.5 版本之前所谓的线程平安的容器,次要指的就是同步容器。不过同步容器有个最大的问题,那就是性能差,所有办法都用 synchronized 来保障互斥,串行度太高了。因而 Java 在 1.5 及之后版本提供了性能更高的容器,咱们个别称为并发容器。

并发容器尽管数量十分多,但仍然是后面咱们提到的四大类:List、Map、Set 和 Queue,上面的并发容器关系图,基本上把咱们常常用的容器都笼罩到了。

一、List

List 外面只有一个实现类就是 CopyOnWriteArrayList。CopyOnWrite,顾名思义就是写的时候会将共享变量新复制一份进去,这样做的益处是读操作齐全无锁。

那 CopyOnWriteArrayList 的实现原理是怎么的呢?上面咱们就来简略介绍一下。

CopyOnWriteArrayList 外部保护了一个数组,成员变量 array 就指向这个外部数组,所有的读操作都是基于 array 进行的,迭代器 Iterator 遍历的就是 array 数组。

如果在遍历 array 的同时,还有一个写操作,例如减少元素,CopyOnWriteArrayList 是如何解决的呢?CopyOnWriteArrayList 会将 array 复制一份,而后在新复制解决的数组上执行减少元素的操作,执行完之后再将 array 指向这个新的数组。读写是能够并行的,遍历操作始终都是基于原 array 执行,而写操作则是基于新 array 进行。

应用 CopyOnWriteArrayList 须要留神的“坑”次要有两个方面。

一个是利用场景,CopyOnWriteArrayList 仅实用于写操作非常少的场景,而且可能容忍读写的短暂不统一。例如下面的例子中,写入的新元素并不能立即被遍历到。

另一个须要留神的是,CopyOnWriteArrayList 迭代器是只读的,不反对增删改。因为迭代器遍历的仅仅是一个快照,而对快照进行增删改是没有意义的。

二、Map

Map 接口的两个实现是 ConcurrentHashMap 和 ConcurrentSkipListMap,它们从利用的角度来看,次要区别在于 ConcurrentHashMap 的 key 是无序的,而 ConcurrentSkipListMap 的 key 是有序的。所以如果你须要保障 key 的程序,就只能应用 ConcurrentSkipListMap。

应用 ConcurrentHashMap 和 ConcurrentSkipListMap 须要留神的中央是,它们的 key 和 value 都不能为空,否则会抛出 NullPointerException 这个运行时异样。上面这个表格总结了 Map 相干的实现类对于 key 和 value 的要求,你能够比照学习。

三、Set

Set 接口的两个实现是 CopyOnWriteArraySet 和 ConcurrentSkipListSet,应用场景能够参考后面讲述的 CopyOnWriteArrayList 和 ConcurrentSkipListMap,它们的原理都是一样的,这里就不再赘述了。

四、Queue

Java 并发包外面 Queue 这类并发容器是最简单的,你能够从以下两个维度来分类。

一个维度是阻塞与非阻塞,所谓阻塞指的是当队列已满时,入队操作阻塞;当队列已空时,出队操作阻塞。

另一个维度是单端与双端,单端指的是只能队尾入队,队首出队;而双端指的是队首队尾皆可入队出队。

Java 并发包里阻塞队列都用 Blocking 关键字标识,单端队列应用 Queue 标识,双端队列应用 Deque 标识。

这两个维度组合后,能够将 Queue 细分为四大类,别离是:

1. 单端阻塞队列 :其实现有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、LinkedTransferQueue、PriorityBlockingQueue 和 DelayQueue。外部个别会持有一个队列,这个队列能够是数组(其实现是 ArrayBlockingQueue)也能够是链表(其实现是 LinkedBlockingQueue);甚至还能够不持有队列(其实现是 SynchronousQueue),此时生产者线程的入队操作必须期待消费者线程的出队操作。而 LinkedTransferQueue 交融 LinkedBlockingQueue 和 SynchronousQueue 的性能,性能比 LinkedBlockingQueue 更好;PriorityBlockingQueue 反对依照优先级出队;DelayQueue 反对延时出队。

2. 双端阻塞队列 :其实现是 LinkedBlockingDeque。

3. 单端非阻塞队列 :其实现是 ConcurrentLinkedQueue。

4. 双端非阻塞队列 :其实现是 ConcurrentLinkedDeque。

另外,应用队列时,须要分外留神队列是否反对有界(所谓有界指的是外部的队列是否有容量限度)。理论工作中,个别都不倡议应用无界的队列,因为数据量大了之后很容易导致 OOM。

下面咱们提到的这些 Queue 中,只有 ArrayBlockingQueue 和 LinkedBlockingQueue 是反对有界的,所以在应用其余无界队列时,肯定要充分考虑是否存在导致 OOM 的隐患。

本文由博客群发一文多发等经营工具平台 OpenWrite 公布

正文完
 0