我需要在invokeAll调用的结果上进行同步吗?

4

我正在改进一个由多个独立步骤组成的算法,以使用并发任务。每个任务将创建多个对象来保存其结果。最终,我希望从控制方法返回所有结果的列表。目前,我的代码大致如下:

private final ExecutorService pool = ...;

// A single task to be performed concurrently with other tasks.
private class WorkHorse implements Callable<Void> {
    private final Collection<X> collect;

    public WorkHorse(Collection<X> collect, ...) {
        this.collect = collect;
    }

    public Void call() {
        for (...) {
            // do work

            synchronized (this.collect) {
                this.collect.add(result);
            }
        }
        return null;
    }
}

// Uses multiple concurrent tasks to compute its result list.
public Collection<X> getResults() {
    // this list is supposed to hold the results
    final Collection<X> collect = new LinkedList<X>();

    final List<WorkHorse> tasks = Arrays.asList(  
        new WorkHorse(collect, ...), new WorkHorse(collect, ...), ...);
    this.pool.invokeAll(tasks);

    // ## A ##
    synchronized (collect) {
        return collect;
    }
}

在工作任务中,我是否需要在“## A ##”处使用synchronized来强制执行与修改操作之间的happens-before关系?或者我可以依赖于所有写操作已经发生在invokeAll返回之后,并且对控制线程可见吗?并且是否有任何理由,不应该从其自己的synchronized块中返回结果集合?

2个回答

3
不需要,invokeAll 的文档说明在返回时应完成所有作业。因此,在到达返回语句时不应再访问 collect。

此外,如果这些不是invokeAll()的语义,同步块将无济于事。你似乎想要一种门闩,直到所有其他线程完成写入才能继续。 - Avi
Brian Goetz在《Java并发编程实战》中指出:“当一个线程A执行了一个同步块时,它所做的所有操作都对另一个线程B可见,只要线程B也执行了同样锁保护的同步块。如果没有同步,就没有这样的保证。”invokeAll文档中的声明似乎并没有放弃这个要求,是吗? - janko
@Zed:我承认当控制方法的同步块被执行时,所有线程都已经运行并完成。然而,我还没有被说服,如果没有同步块,工作任务的所有修改是否能够确保对控制方法可见。 - janko
@janko:这是否也意味着,因为您没有在“collect = new LinkedList<X>()”上放置同步,所以您的一些线程可能会将collect视为未初始化,从而报告空指针异常? - Zed
@Zed:不会,因为写入“collect = new LinkedList<X>()”发生在WorkHorses的构造之前,因此保证对它们可见。有关内存可见性的复杂性,请参见http://java.sun.com/javase/6/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility...然而,链接页面列出的扩展保证使我的最后一个同步块变得多余,我想。 - janko
显示剩余2条评论

0

如果你已经有第一个同步块了,那么你就不需要第二个 synchronized。正如 Zed 所指出的,invokeAll() 会阻塞直到所有任务完成。同时,对于 add() 的同步将确保对集合的更改对所有线程都可见,包括原始调用线程。

至于你是否需要第一个同步块(你没有问),我尝试删除两个 synchronized 块,但实际上无法使其失败,但将其放在那里可能是更安全的选择。根据 LinkedList 的 javadoc:

如果多个线程同时访问 LinkedList,并且其中至少一个线程对列表进行结构修改,则必须在外部进行同步。

其他“第二代” Collection 实现也有类似的警告。

顺便提一下,对于集合本身进行同步并没有什么神奇的地方。您可以在外部类中声明一个单独的互斥体(任何旧的Object都可以),或者在外部类实例上进行同步,只要所有的WorkHorse都在同一个对象上进行同步即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接