文章目录
声明:
本博客是本人在学习《实战 Java 高并发程序设计》后整理的笔记,旨在方便复习和回顾,并非用作商业用途。
本博客已标明出处,如有侵权请告知,马上删除。
3.3 不要重复发明轮子:JDK 的并发容器
除了提供诸如同步控制,线程池等基本工具外,为了提高开发人员的效率,JDK 还为大家准备了一大批好用的容器类,可以大大减少开发工作量。大家应该都听说过一种说法,所谓程序就是 “算法+数据结构”,这些容器类就是为大家准备好的线程数据结构。你可以在里面找到链表、HashMap、队列等。当然,它们都是线程安全的。
在这里,我也打算花一些篇幅为大家介绍一下这些工具类。这些容器类的封装都是非常完善并且 “平易近人” 的,也就是说只要你有那么一点点的编程经验,就可以非常容易地使用这些容器。因此,我可能会花更多的时间来分析这些工具的具体实现,希望起到抛砖引玉的作用。
3.3.1 超好用的工具类:并发集合简介
JDK 提供的这些容器大部分在 java.util.concurrent 包中。我先提纲挈领地介绍一下它们,初次露脸,大家只需要知道他们的作用即可。有关具体的实现和注意事项,在后面我会慢慢道来。
- ConcurrentHashMap:这是一个高效的并发 HashMap。你可以理解为一个线程安全的 HashMap。
- CopyOnWriteArrayList:这是一个 List,从名字看就是和 ArrayList 是一族的。在读多写少的场合,这个 List 的性能非常好,远远好于 Vector。
- ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList。
- BlockingQueue:这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。
- ConcurrentSkipListMap:跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。
除了以上并发包中的专有数据结构外,java.util 下的 Vector 是线程安全的(虽然性能和上述专用工具没得比),另外 Collections 工具类可以帮助我们将任意集合包装成线程安全的集合。
3.3.2 线程安全的 HashMap
在之前的章节中,已经给大家展示了在多线程环境中使用 HashMap 所带来的问题。那如果需要一个线程安全的 HashMap 应该怎么做呢?
一种可行的方法是使用 Collections.synchronizedMap() 方法包装我们的 HashMap。如下代码,产生的 HashMap 就是线程安全的:
Collections.synchronizedMap() 会生成一个名为 SynchronizedMap的Map。它使用委托,将自己所有 Map 相关的功能交给传入的 HashMap 实现,而自己则主要负责保证线程安全。
具体参考下面的实现,首先 SynchronizedMap 内包装了一个 Map。
通过 mutex 实现对这个 m 的互斥操作。比如,对于 Map.get() 方法,它的实现如下:
而其他所有相关的 Map 操作都会使用这个 mutex 进行同步。从而实现线程安全。
这个包装的 Map 可以满足线程安全的要求。但是,它在多线程环境中的性能表现并不算太好。无论是对 Map 的读取或者写入,都需要获得 mutex 的锁,这会导致所有对 Map 的操作全部进入等待状态,直到 mutex 锁可用。如果并发级别不高,一般也够用。但是,在高并发环境中,我们也有必要寻求新的解决方案。
一个更加专业的并发 HashMap 是 ConcurrentHashMap。它位于 java.util.concurrent 包内。它专门为并发进行了性能优化,因此,更加适合多线程的场合。
有关 ConcurrentHashMap 的具体实现细节,大家可以参考 “第4章 锁的优化及注意事项” 一章。我们将在那里给出更加详细的实现说明。
3.3.3 有关 List 的线程安全
队列、链表之类的数据结构也是极其常用的,几乎所有的应用程序都会与之相关。在 Java 中,ArrayList 和 Vector 都是使用数组作为其内部实现。两者最大的不同在于 Vector 是线程安全的,而 ArrayList 不是。此外,LinkedList 使用链表的数据结构实现了 List。但是很不幸,LinkedList 并不是线程安全的,不过参考前面对 HashMap 的包装,在这里我们也可以使用 Collections.synchronizedList() 方法来包装任意 List,如下所示:
此时生成的 List 对象就是线程安全的。
3.3.4 高效读写的队列:深度剖析 ConcurrentLinkedQueue
队列 Queue 也是常用的数据结构之一。在 JDK 中提供了一个 ConcurrentLinkedQueue 类用来实现高并发的队列。从名字可以看到,这个队列使用链表作为其数据结构。有关 ConcurrentLinkedQueue 的性能测试,大家可以自行尝试。这里限于篇幅就不再给出性能测试的代码。大家只要知道 ConcurrentLinkedQueue 应该算是在高并发环境中性能最好的队列就可以了。它之所有能有很好的性能,是因为其内部复杂的实现。
在这里,我更加愿意花一些篇幅来简单介绍一下 ConcurrentLinked