我正在重新阅读Java Concurrency In Practice,我不确定我是否完全理解有关不可变性和安全发布的章节.
这本书的内容是:
任何线程都可以安全地使用不可变对象而无需额外的同步,即使不使用同步来发布它们也是如此.
我不明白的是,为什么有人(有兴趣使他的代码正确)不安全地发布一些参考?
如果对象是不可变的,并且它是不安全地发布的,我理解获得对该对象的引用的任何其他线程将看到其正确的状态,因为由适当的不变性提供的保证(使用final字段等).
但是如果发布是不安全的,那么另一个线程可能仍然会null在发布之后看到或者之前的引用,而不是对不可变对象的引用,这对我来说似乎是没有人愿意的.
如果使用安全发布来确保所有线程都能看到新引用,那么即使对象实际上是不可变的(没有final字段,但也无法将它们静音),那么一切都是安全的.正如书中所说:
安全发布的有效不可变对象可以被任何线程安全地使用而无需额外的同步.
那么,为什么不变性(与有效不变性相比)如此重要?在什么情况下需要不安全的出版物?
我想确保根据Java内存模型正确理解'有效不可变对象'的行为.
假设我们有一个可变类,我们希望将其发布为有效的不可变类:
class Outworld {
// This MAY be accessed by multiple threads
public static volatile MutableLong published;
}
// This class is mutable
class MutableLong {
private long value;
public MutableLong(long value) {
this.value = value;
}
public void increment() {
value++;
}
public long get() {
return value;
}
}
Run Code Online (Sandbox Code Playgroud)
我们执行以下操作:
// Create a mutable object and modify it
MutableLong val = new MutableLong(1);
val.increment();
val.increment();
// No more modifications
// UPDATED: Let's say for this example we …Run Code Online (Sandbox Code Playgroud) 在 Java Concurrency In Practice 中,作者表示
是否意味着以下习语可以安全地发布不可变对象?
public static List<ImmutableObject> list = new ArrayList<ImmutableObject>();
// thread A invokes this method first
public static void methodA () {
list.add(new ImmutableObject());
}
// thread B invokes this method later
public static ImmutableObject methodB () {
return list.get(0);
}
Run Code Online (Sandbox Code Playgroud)
会有数据竞赛吗?(这意味着线程 B 可能无法看到线程 A 添加的列表中的不可变对象)
非常感谢。
更进一步,作者说如果Resource是不可变的,下面的代码是安全的。
@NotThreadSafe
public class UnsafeLazyInitialization {
private static Resource resource;
public static Resource getInstance() {
if (resource == null)
resource = new Resource(); // unsafe publication …Run Code Online (Sandbox Code Playgroud) 这是来自JCiP的示例。
public class Unsafe {
// Unsafe publication
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
}
public class Holder {
private int n;
public Holder(int n) {
this.n = n;
}
public void assertSanity() {
if (n != n) {
throw new AssertionError("This statement is false.");
}
}
}
Run Code Online (Sandbox Code Playgroud)
在第34页上:
[15]这里的问题不是Holder类本身,而是Holder没有正确发布。但是,可以通过将n字段声明为final来使Holder免受不适当发布的影响,这将使Holder不变。
从这个答案:
final的规范(请参阅@andersoj的答案)保证当构造函数返回时,将对final字段进行适当的初始化(从所有线程可见)。
从维基:
例如,在Java中,如果已经内联了对构造函数的调用,则一旦分配了存储空间,但是在内联的构造函数初始化对象之前,可以立即更新共享变量。
我的问题是:
因为:(可能不正确,我不知道。)
a)可以在内联构造函数初始化对象之前立即更新共享变量。
b)只有在构造函数返回时,才能保证对final字段进行正确的初始化(从所有线程可见)。
另一个线程是否可能看到默认值holder.n?(即,另一个线程holder在holder构造函数返回之前获得对它的引用。)
如果是这样,那么您如何解释以下声明?
通过将n字段声明为final,可以使Holder免受不适当发布的影响,这将使Holder不可变
编辑: …