java-线程安全的从集合中查找和删除对象
作者:互联网
我不知道该如何做以下事情
class MyCollection<E> implements Collection<E> {
@Nullable E findAndRemove(Predicate<E> predicate) {
for (E e : this) {
if (predicate.test(e)) {
remove(e);
return e;
}
}
return null;
}
}
以线程安全的方式.它实际上不必一定是Collection,因为唯一需要的操作是add和findAndRemove.注意
> removeIf是不相关的,因为它删除所有匹配的元素并且不提供任何回报
>让我们假设同步不够好
> CopyOnWriteArrayList可能会做,但是Javadoc说“当…遍历操作大大超过变异时,效率很高”,这在这里肯定是不正确的
>不允许两次返回相同的元素(除非在此期间被重新添加),这在天真地使用CopyOnWriteArrayList时很容易发生
>返回哪个匹配元素无关紧要
关于过早的优化和XY问题:是的,我知道!当我闲逛一些可能需要或可能不需要一天的事情时,我遇到了这个问题,但是我发现这个问题本身很有趣.
解决方法:
由于只需要add和findAndRemove方法,因此某些类型的并发哈希是很自然的选择,因为自Java 1.5以来已经有了很好的实现(ConcurrentHashMap).现在,由于实际上并不需要Map,而是Set,我们可以使用ConcurrentHashMap.newKeySet()(无论如何,因为Java 8)来创建并发集合,并使用与并发地图相同的实现.
然后,给定一个并发集,我们可以在上面使用您的循环,并乐观地删除该元素,然后继续继续搜索失败(这意味着线程会同时删除匹配的元素):
class MyCollection<E> {
private Set<E> underlying = ConcurrentHashMap.newKeySet();
void add(E elem) {
underlying.add(elem);
}
@Nullable E findAndRemove(Predicate<E> predicate) {
for (E e : underlying) {
if (predicate.test(e) && remove(e)) {
return e;
}
}
return null;
}
}
关于您的示例代码,唯一真正的变化是我们检查了Set.remove()
的结果,以查看该元素是否被实际删除.对于并发集,此操作“安全”-即,只有实际删除对象的线程才能看到true,因此只有在实际删除该元素的情况下,此设置才可以正确返回该元素,然后其他线程将无法返回该元素.
它应该满足您的所有要求并执行基本的并发映射实现,而在现代JDK上这是“非常好”的.
请注意,使用Set意味着不允许重复元素.从您的描述中还不清楚您是否计划支持重复项,但是如果您打算这样做,则可以使用基于并发multimap1的相同方法,也可以仅使用ConcurrentHashMap< E,AtomicInteger> ;,其中AtomicInteger引用一个引用如果对具有相同键的元素数量进行计数,则add和findAndRemove方法将操纵引用count2. 1但是,在快速搜索中,我找不到并行多图的明显开源实现.请注意,您实际上并不需要具有所有功能的大写M Multimap实现-您实际上只需要“某些多集”即可添加元素,遍历可用元素并“删除”元素(即,递减).其引用计数在集合中). 2我实际上是在掩盖引用计数实现的一些细节,因为在这种情况下,可能会将refcount递减为零的线程与调用同一元素的add的任何线程之间可能存在竞争,这可能会使refcount递增至零以上(但该条目已被删除).可以避免这种情况,但是由于您是否要支持重复项尚不清楚,因此我不做详细介绍.
标签:multithreading,collections,thread-safety,java-util-concurrent,java 来源: https://codeday.me/bug/20191111/2018589.html