确定同步范围?

Mik*_*ike 5 java theory concurrency multithreading

为了提高我对并发问题的理解,我正在研究以下场景(编辑:我已经将示例从List更改为Runtime,这更接近我正在尝试的内容):

public class Example {
    private final Object lock = new Object();
    private final Runtime runtime = Runtime.getRuntime();
    public void add(Object o) { 
        synchronized (lock) { runtime.exec(program + " -add "+o); } 
    }
    public Object[] getAll() { 
        synchronized (lock) { return runtime.exec(program + " -list "); }
    }
    public void remove(Object o) { 
        synchronized (lock) { runtime.exec(program + " -remove "+o); } 
    }
}
Run Code Online (Sandbox Code Playgroud)

就目前而言,每个方法在独立使用时都是线程安全的.现在,我想弄清楚的是如何处理调用类希望调用的位置:

for (Object o : example.getAll()) {
    // problems if multiple threads perform this operation concurrently
    example.remove(b); 
}
Run Code Online (Sandbox Code Playgroud)

但是如上所述,无法保证状态在调用getAll()和调用remove()之间是一致的.如果多个线程调用它,我将遇到麻烦.所以我的问题是 - 我应该如何让开发人员以线程安全的方式执行操作?理想情况下,我希望以一种使开发人员难以避免/错过的方式强制执行线程安全,但同时并不复杂.到目前为止我可以想到三个选项:

答:将锁定为"this",因此调用代码可以访问同步对象,然后可以包装代码块.缺点:难以在编译时强制执行:

synchronized (example) {
    for (Object o : example.getAll()) {
        example.remove(b);
    }
}
Run Code Online (Sandbox Code Playgroud)

B:将组合代码放入Example类中 - 并且可以从优化实现中受益,如本例所示.缺点:添加扩展的痛苦,以及潜在的混合无关逻辑:

public class Example {
   ...
   public void removeAll() { 
       synchronized (lock) { Runtime.exec(program + " -clear"); } 
   }
}
Run Code Online (Sandbox Code Playgroud)

C:提供一个Closure类.缺点:过多的代码,可能过于慷慨的同步块,实际上可以使死锁变得更容易:

public interface ExampleClosure {
    public void execute(Example example);
}
public Class Example {
    ...
    public void execute(ExampleClosure closure) { 
        synchronized (this) { closure.execute(this); } 
    }
}

example.execute(new ExampleClosure() {
        public void execute(Example example) { 
            for (Object o : example.getAll()) {
                example.remove(b);
            }
        }
    }
);
Run Code Online (Sandbox Code Playgroud)

有什么我想念的吗?如何确定同步的范围以确保代码是线程安全的?

Joh*_*int 2

我认为每个人都忽略了他真正的问题。当迭代新的对象数组并尝试一次删除一个时,问题在技术上仍然是不安全的(尽管 ArrayList 植入不会爆炸,但它只是不会达到预期的结果)。

即使使用 CopyOnWriteArrayList,当您尝试删除时,也可能在当前列表上读取过时的内容。

您提供的两个建议都很好(A 和 B)。我的一般建议是 B。使集合线程安全是非常困难的。一个好的方法是为客户端提供尽可能少的功能(在合理范围内)。因此,提供removeAll方法并删除getAll方法就足够了。

现在您可以同时说,“好吧,我想保持 API 的原样,让客户端担心额外的线程安全性”。如果是这种情况,请记录线程安全性。记录“查找和修改”操作既非原子又非线程安全的事实。

今天的并发列表实现对于所提供的单个函数来说都是线程安全的(get、remove add 都是线程安全的)。但复合函数并非如此,最好的办法就是记录如何使它们线程安全。