3
public class ObjectA {

   private void foo() {
       MutableObject mo = new MutableObject();
       Runnable objectB = new ObjectB(mo);
       new Thread(objectB).start();
   }

}

public class ObjectB implements Runnable {

    private MutableObject mo;

    public ObjectB(MutableObject mo) {
        this.mo = mo;
    }

    public void run() {
       //read some field from mo
    }
}

从上面的代码示例可以看出,我将一个可变对象传递给一个实现 Runnable 的类,并将在另一个线程中使用该可变对象。这是危险的,因为 ObjectA.foo() 在启动新线程后仍然可以改变可变对象的状态。在这里确保线程安全的首选方法是什么?将 MutableObject 传递给 ObjectB 时是否应该复制它?可变对象是否应确保内部正确同步?我以前曾多次遇到过这种情况,尤其是在尝试在许多 GUI 应用程序中使用 SwingWorker 时。我通常会尝试确保仅将不可变对象引用传递给将在另一个线程中使用它们的类,但有时这可能很困难。

4

4 回答 4

3

这是一个很难回答的问题,不幸的是,答案是“视情况而定”。当涉及到你的类的线程安全时,你有三个选择:

  1. 让它不可变,那么你不必担心。但这不是你要问的。
  2. 使其成为线程安全的。也就是说,在类内部提供足够的并发控制,客户端代码不必担心并发线程修改对象。
  3. 使其不是线程安全的,并强制客户端代码进行某种外部同步。

你本质上是在问你应该使用#2还是#3。您担心其他开发人员使用该类但不知道它需要外部同步的情况。我喜欢使用JCIP 注释 @ThreadSafe @Immutable @NotThreadSafe作为记录并发意图的一种方式。这不是万无一失的,因为开发人员仍然必须阅读文档,但如果团队中的每个人都理解这些注释并始终如一地应用它们,它确实会让事情变得更清晰。

对于您的示例,如果您想让该类不是线程安全的,您可以使用AtomicReference它来使其清晰并提供同步。

public class ObjectA {

  private void foo() {
     MutableObject mo = new MutableObject();
     Runnable objectB = new ObjectB(new AtomicReference<>( mo ) );
     new Thread(objectB).start();
  }
}

public class ObjectB implements Runnable {

  private AtomicReference<MutableObject> mo;

  public ObjectB(AtomicReference<MutableObject> mo) {
    this.mo = mo;
  }

  public void run() {
   //read some field from mo
   mo.get().readSomeField();
  }
}
于 2012-09-05T20:50:12.477 回答
1

我认为你过于复杂了。如果它是示例(一个没有保留引用的局部变量),您应该相信没有人会尝试写入它。如果可能的话,如果它更复杂(A.foo()有更多的 LOC),那么创建它只是为了传递给线程。

new Thread(new MutableObject()).start();

如果不是(由于初始化),请在一个块中声明它,以便它立即超出范围,甚至可能在单独的私有方法中。

{
   MutableObject mo = new MutableObject();    
   Runnable objectB = new ObjectB(mo);    
   new Thread(objectB).start();    
}
....
于 2012-09-05T20:24:12.577 回答
1

复制对象。您不会有任何奇怪的可见性问题,因为您将副本传递给新线程。Thread.start 总是发生在新线程进入其运行方法之前。如果更改此代码以将对象传递给现有线程,则需要适当的同步。我推荐一个来自 Java.util.concurrent 的阻塞队列。

于 2012-09-05T20:29:26.743 回答
1

如果不知道你的具体情况,这个问题将很难准确回答。答案完全取决于MutableObject代表什么,有多少其他线程可以同时修改它,以及读取对象的线程是否关心在读取对象时其状态是否发生变化。

就线程安全而言,内部同步所有读取和写入MutableObject被证明是“最安全”的事情,但它是以性能为代价的。如果读取和写入的争用非常高,那么您的程序可能会遇到性能问题。您可以通过牺牲一些互斥保证来获得更好的性能 - 这些牺牲是否值得性能提升完全取决于您要解决的具体问题。

你也可以玩一些关于“内部同步”你的游戏MutableObject,如果那是你最终要做的。如果您还没有,我建议您阅读 和 之间的区别volatilesynchronized并了解如何使用它们来确保不同情况下的线程安全。

于 2012-09-05T20:29:31.333 回答