在Java中使用不纯函数迭代Collection的最佳实践



假设这是一个用例:我想更新类中的Hashmap缓存。我有一组键和一些条件,我想应用于键和用键检索的值。


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
public class App {
Set<String> keysToUpdate;
HashMap<String, List<String>> cache;
void buildCache(){
keysToUpdate.stream()
.filter(k -> true)  // some filter
.forEach(
key ->{
// now values list comes from outside the stream pipeline
List<String> values = cache.computeIfAbsent(key, k -> new ArrayList<>());
getValuesforKey(key)
.stream()
.filter(k -> true) //another filter
// side effects are introduced
.forEach(value -> {
//some other operation, for example logging the values added like
// log.info("{} value added", value);
values.add(value);
}
);
}
);
}
private List<String> getValuesforKey(String key) {
//some method to get the values for the key
return new ArrayList<>();
}
}

我们被告知,像这样的共享可变性是不好的,因为执行是不确定的,但在这种特定情况下,我将向哈希映射添加值,如果我知道keyToUpdate不包含重复值,我就不在乎执行顺序。

还有其他方面我没有考虑过吗?如果流是并行的,这个代码安全吗?

如果没有,使用集合的迭代器会解决问题吗?(下面的代码(。还是最好使用命令式编程?在什么情况下,流中的共享可变性是可以的

public class App {
Set<String> keysToUpdate;
HashMap<String, List<String>> cache;
void buildCache(){
keysToUpdate.stream()
.filter(k -> true)// some filter
.collect(Collectors.toList()) // Collect before iterating
.forEach(
key ->{
// now values list comes from outside the stream pipeline
List<String> values = cache.computeIfAbsent(key, k -> new ArrayList<>());
getValuesforKey(key)
.stream()
.filter(k -> true) 、、another filter
.collect(Collectors.toList()) // Collect before iterating
// side effects are introduced
.forEach(value -> {
//some other operation, for example logging the values added like
// log.info("{} value added", value);
values.add(value);
}
);
}
);
}
private List<String> getValuesforKey(String key) {
//some method to get the values for the key
return new ArrayList<>();
}
}

在处理多线程时,要回答的问题是是否会出现竞争条件(即,如果两个或多个不同的线程可能同时访问同一资源,其中至少有一个线程试图修改该资源(。

在您的示例中,如果请求的密钥不存在,computeIfAbsent方法将修改映射。因此,两个线程可能会修改同一资源(cache对象(。为了避免这种情况,您可以通过使用Collections.synchronizedMap()(在buildCache方法的开头(获得映射的线程安全版本,然后对返回的映射进行操作。

对于values列表,安全性取决于两个线程是否可以对同一个密钥进行操作,从而修改同一列表。在您的示例中,密钥是从集合中获得的唯一密钥,因此代码是安全的。

附带说明:性能的预期提高取决于getValuesForKey方法必须执行的处理量。如果可以忽略不计,那么大多数线程将只等待映射上的锁定,从而使性能提高也最小化。

最新更新