如何在Java中并行化循环

如何在Java中并行化循环,java,multithreading,java-threads,Java,Multithreading,Java Threads,在下面的代码中,对HashSet的每个元素调用一个本地方法。如果它返回一个特殊值,我们就停止循环。否则,我们将每个返回值添加到一个新的HashSet HashSet<Object> myHashSet=…; HashSet<Object> mySecondHashSet=…; for (Object s : myHashSet) { Object value = my_method(s); if(value==specialValue)

在下面的代码中,对HashSet的每个元素调用一个本地方法。如果它返回一个特殊值,我们就停止循环。否则,我们将每个返回值添加到一个新的HashSet

HashSet<Object> myHashSet=…; 
HashSet<Object> mySecondHashSet=…; 

for (Object s : myHashSet) {
    Object value = my_method(s);
    if(value==specialValue)
        return value; 
    else 
        mySecondHashSet.add(value);
 }
HashSet myHashSet=…;
HashSet mySecondHashSet=…;
用于(对象s:myHashSet){
对象值=我的方法;
如果(值==特殊值)
返回值;
其他的
添加(值);
}

我想把这个过程简化。HashSet中没有一个对象有任何共同的对象(它是一个树状结构),因此我知道它们可以在没有任何同步问题的情况下运行。我如何修改代码,使我的_方法的每次调用都开始一个新的步骤,并且如果其中一个线程的计算结果为特殊值,则所有线程都停止而不返回,并且返回特殊值

考虑到java 8,这可能相对简单,但不会保留初始代码语义:

在这种情况下,您所需要的是返回特殊值,一旦你击中它

if (myHashSet.parallelStream()
             .map(x -> method(x))
             .anyMatch(x -> x == specialValue)) {

    return specialValue;
}
如果您需要保留转换后的值直到满足特殊值,那么您已经在注释中从@Elliot得到了答案,同时需要指出语义与原始代码不同,因为不会保留任何订购者


虽然它尚未被检查,但我希望以下内容得到优化,并在达到所需的特殊值时停止:

if (myHashSet.parallelStream()
             .anyMatch(x -> method(x) == specialValue)) {

    return specialValue;
}

我会在两次过程中完成:

  • 查找转换后的集合元素是否与特殊值匹配
  • 将它们转换为一个集合
  • 为每个转换启动一个新线程太重了,这会使您的机器崩溃(除非您只有很少的元素,在这种情况下,并行化可能不值得付出努力)

    为了避免使用
    my_method
    对值进行两次转换,您可以惰性地进行转换并将结果记录下来:

    private class Memoized {
        private Object value;
        private Object transformed;
        private Function<Object, Object> transform;
    
        public Memoized(Object value, Function<Object, Object> transform) {
            this.value = value;
        }
    
        public Object getTransformed() {
            if (transformed == null) {
                transformed = transform.apply(value);
            }
            return transformed;
        }
    }
    
    私人课堂备忘{
    私人客体价值;
    私有客体转化;
    私有功能转换;
    公共记忆(对象值、功能转换){
    这个值=值;
    }
    公共对象getTransformed(){
    if(transformed==null){
    转换=转换。应用(值);
    }
    收益转化;
    }
    }
    
    然后您可以使用以下代码:

    Set<Memoized> memoizeds = 
        myHashSet.stream() // no need to go parallel here
                 .map(o -> new Memoized(o, this::my_method))
                 .collect(Collectors.toSet());
    
    Optional<Memoized> matching = memoized.parallelStream()
        .filter(m -> m.getTransformed().equals(specialValue))
        .findAny();
    
    if (matching.isPresent()) {
        return matching.get().getTransformed();
    }
    
    Set<Object> allTransformed = 
        memoized.parallelStream() 
                .map(m -> m.getTransformed())
                .collect(Collectors.toSet());
    
    设置备忘=
    myHashSet.stream()//这里不需要并行
    .map(o->new memonized(o,this::my_方法))
    .collect(收集器.toSet());
    可选匹配=memonized.parallelStream()
    .filter(m->m.getTransformed().equals(specialValue))
    .findAny();
    if(匹配.isPresent()){
    返回匹配的.get().getTransformed();
    }
    设置所有转换=
    memonized.parallelStream()
    .map(m->m.getTransformed())
    .collect(收集器.toSet());
    
    这并不完全是你想要的,因为这还不完全清楚;但是你想要像这样的东西吗?Set mySecondHashSet=myHashSet.stream().parallel().map(x->my_method(x)).collector(Collectors.toSet());if(mySecondHashSet.contains(specialValue)){return specialValue;}你的主张是什么“不需要同步"不正确。哈希集不是线程安全的;以并行方式插入它可能会丢失更新或损坏。如果要并行化,则需要插入到线程安全集,或收集到线程本地集并合并这些集。啊,好的,我的意思是,没有任何对象共享任何变量。我应该使用哈希集的instaed数据结构?这段代码是否会在包含特殊值后立即停止执行?不,不会。主要目标是什么?检查特殊值或存储所有值直到找到特殊值?还是两者都有?如果我找到特殊值,我不需要继续处理。因此,在找到它后立即停止更有效。Continuing可能会吃掉parralization带来的任何收益。是的,这是正确的,但是mySecondHashSet的原因是什么?使用并行方法,你可能会在串行方法之前找到特殊值。因为如果我没有找到特殊值,我需要保留它们。我明白了,我这里的主要目标确实是优化执行me。我不需要记忆,因为我可以确定,由于初始数组不包含重复项,因此不能计算两次相同的值。您错过了记忆的要点。这样可以避免对同一对象调用两次我的方法(
    )。第一次检查转换后的值是否与特殊值匹配,第二次检查创建集合时。我仍然需要在elementSet上进行两次检查,所以O(2n)。如果我不能从并行化中获得加速,我可能会保留for循环。不,你仍然没有抓住要点。由于记忆化,每个元素都是集合,最多只传递给我的_方法一次。我也不太明白你为什么认为不能从并行化中获得加速。你没有注意到这个解决方案吗使用并行流?我明白了。谢谢,我会实现这个。我想知道,哈希表的大小在执行过程中变化很大。你认为这个机器操作系统对于一个小表来说“太mcuh”了吗?我可以测试它的大小,并且只在大于一个大小的范围内使用它,对于较小的表使用for循环。