为什么没有ConcurrentHashSet对应ConcurrentHashMap? ConcurrentHashMap是Java中的一个线程安全的哈希表实现,它可以在多线程环境下高效地进行并发访问。它提供了一些方法来支持并发操作,如putIfAbsent、remove等。 然而,与ConcurrentHashMap不同的是,Java中没有提供ConcurrentHashSet类。ConcurrentHashSet是一个线程安全的Set实现,可以在多线程环境下进行并发访问。它与ConcurrentHa
为什么没有ConcurrentHashSet对应ConcurrentHashMap? ConcurrentHashMap是Java中的一个线程安全的哈希表实现,它可以在多线程环境下高效地进行并发访问。它提供了一些方法来支持并发操作,如putIfAbsent、remove等。 然而,与ConcurrentHashMap不同的是,Java中没有提供ConcurrentHashSet类。ConcurrentHashSet是一个线程安全的Set实现,可以在多线程环境下进行并发访问。它与ConcurrentHa
HashSet是基于HashMap实现的。
如果我们看一下HashSet的实现,可以发现一切都是在HashMap下进行管理的。
而我们知道HashMap不是线程安全的。这就是为什么Java中有ConcurrentHashMap的原因。
基于这个,我很困惑为什么我们没有一个基于ConcurrentHashMap的ConcurrentHashSet呢?
还有其他我可能忽略的东西吗?我需要在多线程环境中使用Set。
另外,如果我想创建自己的ConcurrentHashSet,只需将HashMap替换为ConcurrentHashMap,其余部分保持不变,可以实现吗?
为什么没有ConcurrentHashSet来对抗ConcurrentHashMap?
在使用Guava 15之后,可以使用以下代码来简单创建ConcurrentHashSet:
Set s = Sets.newConcurrentHashSet();
这总是一个噩梦。如果你有一个不指示是否线程安全的集合或映射,你会发现维护中发生各种各样的危险和灾难。我总是希望有一种指示集合(或者不是)线程安全的类型。
方法描述文字直接是“创建由哈希映射支持的线程安全集合”。
正如我所说的,缺少ConcurrentSet
为什么没有ConcurrentHashSet对应于ConcurrentHashMap?
在Java中,我们经常需要在多线程环境中使用集合类。ConcurrentHashMap是一个线程安全的哈希表实现,可以在并发环境下使用。但是,Java标准库中没有提供ConcurrentHashSet这样的线程安全的集合类,这是为什么呢?
一个常见的解决方案是使用ConcurrentHashMap来实现线程安全的Set集合。可以使用Collections类的newSetFromMap方法将ConcurrentHashMap包装成一个Set集合。下面是一个示例代码:
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
这段代码将一个ConcurrentHashMap包装成一个Set集合,实现了线程安全的操作。但是,为什么Java标准库没有提供一个ConcurrentHashSet类呢?
实际上,ConcurrentHashMap已经提供了类似ConcurrentHashSet功能的方法,即newKeySet()方法。这个方法可以创建一个线程安全的Set集合,它使用了类似的底层实现。下面是一个示例代码:
Set<String> mySet = ConcurrentHashMap.newKeySet();
这段代码使用了newKeySet()方法来创建一个线程安全的Set集合,代码更加简洁易读。因此,在2021年,推荐使用ConcurrentHashMap的newKeySet()方法来实现线程安全的Set集合。
Java标准库中没有提供ConcurrentHashSet类,但可以使用ConcurrentHashMap的newKeySet()方法来实现线程安全的Set集合。这种方式更加简洁易读,是推荐的解决方案。
为什么没有ConcurrentHashSet对应ConcurrentHashMap?
Java中没有内建的类型ConcurrentHashSet是因为你总是可以从一个map中派生出一个set。由于存在许多类型的map,你可以使用一个方法从给定的map(或map类)中生成一个set。
在Java 8之前,你可以通过使用Collections.newSetFromMap(map)从ConcurrentHashMap创建一个支持ConcurrentHashSet的并发哈希集。这个方法会返回一个Set接口的实现。
在Java 8中,你可以通过ConcurrentHashMap.newKeySet()获取一个并发哈希集的视图。这比旧的newSetFromMap要简单一些,因为它不需要传入一个空map对象。但它只适用于ConcurrentHashMap。
无论如何,Java的设计者可以在创建新的map接口时每次创建一个新的set接口,但这种模式在第三方创建他们自己的map时是不可能强制执行的。最好的方法是有一些派生新set的静态方法;这种方法总是有效的,即使你创建自己的map实现。
使用这种方式从ConcurrentHashMap创建set,你会失去从ConcurrentHashMap获取的好处吗?
没有什么好处可以失去。newSetFromMap的实现可以在docjar.com/html/api/java/util/Collections.java.html的第3841行找到。它只是一个包装器...
对于set的add操作是否等同于putIfAbsent?ConcurrentMap中的其他操作在Set中都没有提供任何功能。Set已经有了一个remove方法,我不明白ConcurrentMap类似替换操作在Set对象上怎么说得通。
我认为使用“ConcurrentSet”的动机与API无关,而与实现有关 - 线程安全但没有普遍锁定 - 例如多个并发读取。
嗯,我觉得我回应的评论在某个时候被删除了。我模糊地记得它问为什么没有ConcurrentSet接口(我的观点是它不会提供任何新的方法;它只是一个标记接口,就像RandomAccess一样。诚然,Set本身基本上是一个标记接口,因为它没有提供新的方法;只是新的约束)。
如果你的元素是Comparable,为什么不直接使用ConcurrentSkipListSet?
ConcurrentSkipListSet有很多(大小)开销,而且查找速度较慢。
我的抱怨是通过这种方式,你会失去HashSet(Collection extends E> c)构造函数,它具有很好的语法糖,例如将List转换为Set。或者我错了吗?
很好的观点。我现在遇到了这个问题。newSetFromMap可以为我们完成这项工作。
我在使用ConcurrentHashMap.newKeySet(以及Collections的替代方法)时发现它无法处理空键,并且在remove调用时会出现NPE(HashSet不会出现这种情况)。
当JDK没有提供真正的ConcurrentSet接口时,真正的问题是你将错过对该Set的一些原子操作,即addIfAbsent,computeIfAbsent等。在JDK8中,你可以自己创建这些操作,因为ConcurrentHashMap.KeySetView允许你访问底层的ConcurrentMap。在JDK7中,你将被限制,可能不得不创建自己的包装器。
当使用这种方法时要小心,因为一些方法的实现是不正确的。只需查看以下链接:Collections.newSetFromMap创建一个SetFromMap。例如,SetFromMap.removeAll方法委托给KeySetView.removeAll,后者继承自ConcurrentHashMap$CollectionView.removeAll。这种方法在批量删除元素时效率非常低下。想象一下,removeAll(Collections.emptySet())会遍历Map中的所有元素而不执行任何操作。在大多数情况下,一个正确实现的ConcurrentHashSet会更好。
至于@JaredR的评论,HashSet的removeAll方法继承自AbstractSet,它至少会迭代两个集合中较小的集合...所以CollectionView的工作方式并不是非常糟糕...特别是如果你更仔细地查看并发map的性能特性...通过始终使用自己的迭代器,它可以使用Iterator.remove来分离一个项,而AbstractSet版本可能会迭代参数然后调用.remove(),这会导致完整的哈希表查找(与Iterator.remove相比,后者可以避免昂贵的检查/图形遍历)。
基本上,如果set/map知道执行“冷remove()”的代价很高,它可以做出(正确的)选择,以避免当两个集合都很大时性能恶化,而以(可能)更差的性能作为代价,当集合很小或为空时。特别是空集合,因为你作为调用者可以在进行查询之前优化并过滤掉空查询。
我的评论与Java8及之前有关。他们在Java9+中已经解决了这个问题,所以现在的方法确实进行了这些检查。
啊,非常感谢你的更新。我们公司还没有迁移到Java9+,所以很高兴能够获取这些信息,以备不时之需(很快)。:-)
那为什么存在ConcurrentSkipListSet,当你可以从ConcurrentSkipListMap派生一个set?
这是一个对作者Doug Lea的一个很好的问题。他明确决定通过包装ConcurrentSkipListMap来构建ConcurrentSkipListSet - 参见fuseyism.com/classpath/doc/java/util/concurrent/...的第65行。我猜想,通过比较map和set的源代码(也许在这种情况下包装map有一些好处?),你可以弄清楚他为什么这样做,但直接问Doug可能更容易。我觉得这可能与跳表作为有序集合而不是使用哈希的无限制集合有关。
这个问题可以扩展到整个Collection API。HashSet只是HashMap的一个包装器,LinkedHashSet是LinkedHashMap的一个包装器,TreeSet只是TreeMap的一个包装器...