【问题标题】:Do I need to synchronize access to immutable types in Java?我是否需要同步对 Java 中不可变类型的访问?
【发布时间】:2011-04-29 21:58:13
【问题描述】:

假设我有这个课程:

class Zoo
{
    protected String bearName;
    protected Double trainerSalary;
    protected Integer monkeyCount;
}

可以一个线程写入这些字段,另一个线程读取它们,而不需要synchronized 访问Zoo 对象吗?

注意:这些值可以彼此分开处理,因此在读取monkeyCount 时更改trainerSalary 并不重要。

编辑

澄清一下,这些字段是可变的;只有它们引用的对象是不可变的

【问题讨论】:

  • 是什么让这些值“不可变”?如果你可以写信给他们,他们需要通过某种形式的同步来保护。
  • @Rodney Gitzel:我认为这意味着 objects 本身是不可变的。您不能保留对 String 的引用并更改内容(您需要创建一个新对象)。
  • @Rodney Gitzel String、Double 和 Integer 在 Java 中都是不可变类型。做任何改变它们的工作只会返回一个新对象。
  • Zoo 在其字段中使用不可变对象并不重要。这并不能使 Zoo 不可变。不变性意味着一旦创建,您将不会更改 Zoo 中的任何内容。现在,如果 Zoo 真的是不可变的,那么您的问题的答案是“不,您不需要同步对 Zoo 的访问”。

标签: java multithreading synchronization thread-safety


【解决方案1】:

从技术上讲,您需要将它们设为finalvolatile 或读取使用synchronzied 写入它们,以保证读者将读取最新的值。正如您现在所拥有的,如果一个线程写入一个值,则不能保证另一个线程将读取相同的值。这是因为读取线程可能会看到缓存的值。多核 CPU 和不同级别的缓存更可能出现这种情况。

这方面的好书是Java Concurrency in Practice

【讨论】:

  • volatilesynchronized 是防止两个不同线程同时从同一个线程获取不同值的关键。
  • 我是否可以建议您在列举替代方案时也提及 java.util.concurrent.atomic 包中的类?这是另一种无需任何显式同步的方式。
【解决方案2】:

对与除 long 或 double 之外的任何类型的字段对应的内存单元的访问和更新保证是原子的(see Concurrent Programming In Java)。这就是为什么人们可能期望您不需要同步对字段的读取访问。但是,Java 内存模型允许线程缓存以前读取的值,以防您重复访问它们,因此您应该将字段标记为 volatile 以确保每个线程都能看到最新的值。

如果您确定没有人会更改字段的值,请将它们设为最终值。在这种情况下,不需要 volatile 字段。

如果字段的值相互依赖,情况就会有所不同。在这种情况下,我建议使用同步设置器,以确保不违反类的不变量。

【讨论】:

  • Accesses and updates to the memory cells corresponding to fields of any type except long or double are guaranteed to be atomic - 真的吗?我以前从未遇到过任何记录这一点的东西。
  • 抱歉,忘记注明出处。来自 Doug Leas 的“Java 并发编程”
【解决方案3】:

正如您所说的那样,同一个包中的另一个类可以更改这些值。这个类不是一成不变的。

现在如果你做了类似的事情

class Zoo
{
    protected final String bearName;
    protected final Double trainerSalary;
    protected final Integer monkeyCount;
}

那么这个类将是不可变的。如果您的程序的逻辑将此类视为不可变的,那么为什么不让它实际上不可变呢?

此外,如果多个线程正在检查和更新相同的值,那么您可能会遇到问题。假设多个线程正在检查和更新monkeyCount,那么monkeyCount 很有可能最终会不正确,因为没有任何东西可以强制这些检查和更新以原子方式发生。

【讨论】:

    【解决方案4】:

    我的 2 美分,来自“Java 编程语言”,第 4 版,14.10.2: “有一个普遍的误解,认为对不可变对象的共享访问不需要任何同步,因为对象的状态永远不会改变。这通常是一个误解,因为它依赖于假设线程将保证看到 不可变对象的初始化状态,不必如此。问题是,虽然共享对象是不可变的,但用于访问共享对象的引用本身是共享的,并且通常是可变的——因此,正确同步的程序必须同步对该共享引用的访问,但程序通常不这样做,因为程序员不承认有必要这样做。例如,假设一个线程创建了一个 String 对象并将对它的引用存储在一个静态字段中。然后第二个线程使用它 访问字符串的引用。根据我们目前所讨论的内容,不能保证第一个线程在构造字符串时写入的值将在第二个线程访问字符串时被第二个线程看到。”

    【讨论】:

      【解决方案5】:

      如果这些变量确实是独立的,那么不,你不需要同步。但是,正如您所注意到的,如果您有

      protected Integer monkeysLeft;
      protected Integer monkeysEatenByBears;
      

      如果这两个变量在逻辑上是相连的,那么您会希望同步访问这对变量。

      【讨论】:

      • 如果一个线程正在写入它们而另一个线程正在按照他的建议读取它们,它们无论如何都需要同步。
      猜你喜欢
      • 1970-01-01
      • 2016-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多