【问题标题】:final fields and thread-safety最终字段和线程安全
【发布时间】:2011-08-22 08:39:56
【问题描述】:

为了线程安全,是否应该是故意不可变的 java 类“final”的所有字段,包括超字段,还是没有修饰方法就足够了?

假设我有一个带有非最终字段的 POJO,其中所有字段都是某个不可变类的类型。这个 POJO 有 getter-setter 和一个设置一些初始值的构造函数。如果我通过淘汰修饰符方法扩展这个 POJO,从而使其不可变,扩展类会是线程安全的吗?

【问题讨论】:

  • 淘汰修饰符方法修饰符方法是什么意思?向所有二传手抛出异常?这将违反Liskov substitution principle。但是是的,这个类将是线程安全的。
  • 是的,抛出运行时异常或用空主体覆盖它们可能带有一些日志记录。我知道它违反了 LSP。

标签: java thread-safety immutability


【解决方案1】:

为了以线程安全的方式使用不带final 字段的有效不可变对象,在初始化后使对象可用于其他线程时,您需要使用安全发布惯用语之一,否则这些线程可以部分看到对象初始化状态(来自Java Concurrency in Practice):

  • 从静态初始化器初始化对象引用;
  • 将对其的引用存储到 volatile 字段或 AtomicReference 中;
  • 将对它的引用存储到正确构造的对象的最终字段中;或
  • 将对它的引用存储到由锁适当保护的字段中。

将不可变对象的字段声明为final 会释放此限制(即,它保证如果其他线程看到对该对象的引用,他们也会看到其final 字段处于完全初始化状态)。但是,一般情况下,它不能保证其他线程可以在对象的引用一经发布就看到它,因此您可能仍需要使用安全发布来确保它。

请注意,如果您的对象实现了接口,则可以使用Collections.unmodifiableList()等使用的方法:

class ImmutableFooWrapper implements IFoo {
    private final IFoo delegate; // final provides safe publication automatically

    public ImmutableFooWrapper(IFoo delegate) {
        this.delegate = delegate;
    }
    ...
}

public IFoo immutableFoo(IFoo foo) {
    return new ImmutableFooWrapper(foo);
}

【讨论】:

  • 感谢您的详细回答。我正在考虑使用委托的相同解决方案,但后来我总是返回到带有扩展名的解决方案,因为我猜“超级”引用也是最终的。我错过了什么?
  • @pcjuzer: super 与并发无关,因此在扩展的情况下,无论如何您都需要安全发布。
  • 那么,这意味着如果你声明一个字段为final,那么如果它正在被一个线程更新,那么其他线程将看不到它,如果它看到那么它将看到更新状态?
【解决方案2】:

是的,它将是不可变的,因此是线程安全的,但前提是字段是私有的。

【讨论】:

  • 为什么这些字段实际上必须是私有的?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多