在本教程中,我们将了解创建线程安全HashSet实例的可能性以及HashSet的ConcurrentHashMap的等价物。此外,我们将研究每种方法的优缺点。
使用ConcurrentHashMap工厂方法的线程安全HashSet
首先,我们将查看公开静态newKeySet()方法的ConcurrentHashMap类。基本上,此方法返回一个尊重java.util.Set接口的实例,并允许使用标准方法,如add()、contains()等。
这可以简单地创建为:
Set<Integer> threadSafeUniqueNumbers = ConcurrentHashMap.newKeySet(); |
此外,返回的Set的性能类似于Has hSet,因为两者都是使用基于哈希的算法实现的。此外,同步逻辑带来的额外开销也很小,因为实现使用了ConcurrentHashMap。
最后,缺点是该方法仅从 Java 8 开始存在。
使用ConcurrentHashMap实例方法的线程安全HashSet
至此,我们已经了解了ConcurrentHashMap 的静态方法。接下来,我们将处理可用于ConcurrentHashMap的实例方法来创建线程安全的Set实例。有两种方法可用,newKeySet()和newKeySet(defaultValue),它们彼此略有不同。
这两种方法都创建了一个与原始map链接的Set。换句话说,每次我们向原始ConcurrentHashMap 添加一个新条目时, Set都会接收该值。此外,让我们看看这两种方法之间的区别。
- newKeySet ()方法
如上所述,newKeySet()公开了一个包含原始映射的所有键的Set 。此方法与newKeySet(defaultValue)的主要区别在于当前方法不支持向Set添加新元素。因此,如果我们尝试调用add()或addAll() 之类的方法,我们将得到 UnsupportedOperationException。
尽管remove(object)或clear()之类的操作按预期工作,但我们需要注意Set上的任何更改都将反映在原始映射map中:
ConcurrentHashMap<Integer,String> numbersMap = new ConcurrentHashMap<>(); |
输出:
Map before remove: {1=One, 2=Two, 3=Three} |
- newKeySet (defaultValue)方法
让我们看看另一种使用地图中的键创建Set的方法。与上面提到的相比,newKeySet(defaultValue)返回一个Set实例,该实例支持通过调用set 上的add()或addAll()来添加新元素。
进一步查看作为参数传递的默认值,这被用作地图中添加的每个新条目的值add()或addAll()方法。以下示例显示了它是如何工作的:
ConcurrentHashMap<Integer,String> numbersMap = new ConcurrentHashMap<>(); |
下面是上面代码的输出:
Map before add: {1=One, 2=Two, 3=Three} |
使用集合实用程序类的线程安全HashSet
让我们使用java.util.Collections 中可用的synchronizedSet()方法来创建一个线程安全的HashSet实例:
Set<Integer> syncNumbers = Collections.synchronizedSet(new HashSet<>()); |
在使用这种方法之前,我们需要意识到它的效率不如上面讨论的那些。与实现低级并发机制的ConcurrentHashMap相比, synchronizedSet()基本上 只是将Set实例包装到同步装饰器中。
使用CopyOnWriteArraySet 的线程安全集
创建线程安全Set实现的最后一种方法是CopyOnWriteArraySet。创建这个Set的实例很简单:
Set<Integer> copyOnArraySet = new CopyOnWriteArraySet<>(); |
尽管使用这个类看起来很有吸引力,但我们需要考虑一些严重的性能缺陷。在幕后,CopyOnWriteArraySet使用Array 而不是HashMap来存储数据。这意味着像contains()或remove()这样的操作有 O(n) 的复杂度,而当使用由ConcurrentHashMap 支持的 Set 时,复杂度是 O(1)。
建议在Set大小通常保持较小且只读操作占多数时使用此实现。
结论
在本文中,我们看到了创建线程安全Set实例的不同可能性,并强调了它们之间的区别。首先我们看到了ConcurrentHashMap.newKeySet() 静态方法。当需要线程安全的HashSet时,这应该是首选。之后我们查看了ConcurrentHashMap静态方法和 用于ConcurrentHashMap 实例的newKeySet()、newKeySet(defaultValue)之间的区别。
最后我们还讨论了集合。synchronizedSet()和CopyOnWriteArraySet 存在性能缺陷。
像往常一样,完整的源代码可以在 GitHub 上找到。