来自 The Java Concurrency In Practice
的说明任何线程都可以安全地使用不可变对象而无需额外的 同步,即使没有使用同步来发布它们
我得到的是线程安全。
Jeremy Manson博客的片段 -
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;
}
}
由于此引用存储在 lastConstructed 中,因此转义构造函数
当然,如果你使lastConstructed volatile(postJDK5 +语义)
,它会工作其中一个问题是 -
杰里米的答复是 -如果lastConstructed是易变的,那么引用是不安全的 发布到另一个线程,然后String不会是不可变的。 正确?
它不是线程安全的,因为它是不可变的,但它是线程安全的,因为lastConstructed是易失性的。
我完全理解它会是线程安全的,因为lastConstructed是易变的,但是我没有得到它不是线程安全的,因为它是不可变的。
为什么呢? Concurrency In Practice中的注释表明,任何线程都可以安全地使用不可变对象(即线程安全保证)。如果某些东西是不可变的,那么它就是线程安全的。
请建议。
答案 0 :(得分:2)
虽然@Peter Lawrey已经解释了设计线程安全和不可变类的细节和问题,并且基于进一步的讨论,我认为问题没有得到直接的答案。所以,我想详细说明一下:
理解短语“不可变对象可以被任何线程安全使用”的主要问题是它本身并不完整。它依赖于这样的前提:任何对象也必须安全地发布为线程安全的。不可变对象也不例外。因此,完整的短语应该是“不可变的,安全发布的对象可以被任何线程安全地使用。”
String
示例中的问题是它允许对象的引用从构造函数中转义,从而向其他线程呈现可能无效的对象状态。例如,如果编译器决定优化构造函数并以性能原因重新排列操作:
public String(byte[] value) {
bytes = new byte[value.length];
lastConstructed = this;
System.arraycopy(value, 0, bytes, 0, value.length);
}
其他一些读取lastConstructed
的线程将能够看到未完全构造的字符串。因此,该类不是线程安全的,即使它的实例是不可变的。
这里我们来到这句话的含义“它不是线程安全的,因为它是不可变的”。这意味着单独的不变性并不能保证线程安全,并且该示例证明了这一点。
使lastConstructed
volatile会强制编译器发出内存屏障,阻止优化器以上述方式重新排列操作。它将保证在分配lastConstructed = this
之前总是发生数组复制。结果,另一个读取lastConstructed
的线程永远不会看到一个未构造的字符串。它还可以保证其他线程始终读取lastConstructed
的实际值。这就是为什么“它会是线程安全的,因为在这种特殊情况下,lastConstructed是易变的”。
答案 1 :(得分:1)
常见的误解是你在Java中有对象字段。您只有引用和原语。这意味着
static String lastConstructed;
字段lastConstructed
是可变引用。它的可见性不是线程安全的。拥有一个不可变对象不会在对该对象的引用上赋予任何属性。
同样,如果你有final
字段,这不会使你的对象不可变。
final Date today = new Date();
today
不是一成不变的,因为您对它进行了一次引用final
。
更精细的是volatile
使用你必须要小心你是在读还是写易变的值。即使你这样做
volatile Date now = new Date();
now.setTime(System.currentTimeMillis()); // no thread safe.
这有两个原因,这不是线程安全的。 1)对now
的访问是读取而不是写入,其次是在写入之前发生的任何情况。需要的是之后的写屏障。你看到的东西似乎是无稽之谈。
now = now; // this adds a write barrier.
一个相关的误解是,如果您使用线程安全集合,那么您执行的任何系列操作也是线程安全的。它有点像仙尘,你只是洒在它周围,你的许多虫子消失了,但这并不意味着你真的是线程安全的。
简而言之,线程安全就像一连串的依赖关系,如果访问数据的任何方面都不是线程安全的,那都不是。
注意:添加一些线程安全结构可以隐藏线程安全问题,但它不能修复它们,它只是意味着JVM或OS或硬件中的某些更改将在以后破坏您的代码。