不可变对象和初始化安全与线程安全



来自的注释Java并发性的实践

不可变对象可以被任何线程安全地使用,而不需要额外的同步,即使不使用同步来发布它们

我得到的是它们是线程安全的

Jeremy Manson blog-

class String {
  // Don't do this, either.
  static String lastConstructed;
  private final byte[] bytes;
  public String(byte[] value) {
    bytes = new byte[value.length];
    System.arraycopy(value, 0, bytes, 0, value.length);
    lastConstructed = this;         
  }
}

由于this引用被存储在 lastconstruct "因此转义构造函数

当然,如果你让lastconstruct_volatile (postJDK5+ semantics)

其中一个问题是-

如果lastconstruct是易失的,但是引用是不安全的发布到另一个线程,那么字符串就不是不可变的。对吧?

Jeremy的回复是-

它不是线程安全的,因为它是不可变的,但它是线程安全的,因为lastconstruct是volatile的。

我完全理解它将是线程安全的,因为lastconstruct是易失性的,但我没有得到它不会是线程安全的,因为它是不可变的

为什么?从并发实践的说明说,不可变对象可以安全地使用任何线程(即线程安全保证)。如果某些东西是不可变的,那么它就是线程安全的

请建议。

虽然@Peter Lawrey已经解释了设计线程安全和不可变类的细节和问题,但是根据进一步的讨论,我认为这个问题还没有得到直接的答案。所以,我想详细说明一下:

理解短语"不可变对象可以被任何线程安全地使用"的主要问题是它本身并不完整。它所依赖的前提是,任何对象也必须安全发布,才能是线程安全的。不可变对象也不例外。所以,完整的短语应该是"不可变的和安全发布的对象可以被任何线程安全地使用。"

String示例中的问题是,它允许对对象的引用从构造函数中转义,从而向其他线程呈现潜在的无效对象状态。例如,如果编译器出于性能原因决定优化构造函数并重新安排操作,则如下:

public String(byte[] value) {
    bytes = new byte[value.length];
    lastConstructed = this;         
    System.arraycopy(value, 0, bytes, 0, value.length);
}

读取lastConstructed的其他线程将能够看到未完全构造的字符串。因此,类不是线程安全的,即使它的实例是不可变的。

这里我们来看看短语"它不会是线程安全的,因为它是不可变的"的含义。这意味着不变性本身并不能保证线程安全,这个例子证明了这一点。

使lastConstructed易失性将迫使编译器发出一个内存屏障,这将阻止优化器以上述方式重新安排操作。它将保证数组复制总是发生在赋值lastConstructed = this之前。因此,读取lastConstructed的另一个线程永远不会看到未构造的字符串。它还可以保证其他线程总是读取lastConstructed的实际值。这就是为什么"它将是线程安全的,因为lastconstruct是易失性的"在这个特殊的例子中

一个常见的误解是Java中有Object字段。你只有引用和原语。这意味着

static String lastConstructed;

字段lastConstructed是一个可变引用。它的可见性不是线程安全的。拥有不可变对象并不会赋予对该对象的引用任何属性。

同样,如果你有一个final字段,这不会使你的对象不可变。

final Date today = new Date();

today不是不可变的,因为你引用了final

一个更微妙的问题是在使用volatile时,你必须小心你是在读还是写volatile值。即使你做了

volatile Date now = new Date();
now.setTime(System.currentTimeMillis()); // no thread safe.

不是线程安全的原因有两个。1)对now的访问是读而不是写,其次,它在任何情况下都发生在写之前。之后需要的是一个写屏障。你所看到的似乎是无意义的东西。

now = now; // this adds a write barrier.

一个相关的误解是,如果你使用线程安全集合,你执行的任何一系列操作也是线程安全的。这有点像仙尘,你只要撒上它,你的许多bug就会消失,但这并不意味着你真的是线程安全的。

简而言之,线程安全就像一个依赖链,如果访问数据的任何方面都不是线程安全的,那么所有方面都不是。

注意:添加一些线程安全结构可以隐藏线程安全问题,但它并不能修复它们,它只是意味着JVM,操作系统或硬件的一些变化将在未来破坏你的代码。

最新更新