gpt4 book ai didi

java - 部分构造的对象/多线程

转载 作者:搜寻专家 更新时间:2023-10-31 20:24:25 25 4
gpt4 key购买 nike

我正在使用 joda,因为它在多线程方面享有盛誉。它大大提高了多线程日期处理的效率,例如通过使所有 Date/Time/DateTime 对象不可变。

但在这种情况下,我不确定 Joda 是否真的在做正确的事情。可能是这样,但我很想看到解释。​​

当调用 DateTime 的 toString() 时,Joda 会执行以下操作:

/* org.joda.time.base.AbstractInstant */
public String toString() {
return ISODateTimeFormat.dateTime().print(this);
}

所有格式化程序都是线程安全的(它们也是不可变的),但是格式化程序工厂是什么:

private static DateTimeFormatter dt;

/* org.joda.time.format.ISODateTimeFormat */
public static DateTimeFormatter dateTime() {
if (dt == null) {
dt = new DateTimeFormatterBuilder()
.append(date())
.append(tTime())
.toFormatter();
}
return dt;
}

这是单线程应用程序中的常见模式,但众所周知,它在多线程环境中容易出错。

我看到了以下危险:

  • 空检查期间的竞争条件 --> 最坏的情况:创建了两个对象。

没问题,因为这只是一个辅助对象(与正常的单例模式情况不同),一个保存在 dt 中,另一个丢失,迟早会被垃圾回收。

  • 在对象完成初始化之前,静态变量可能指向部分构造的对象

(在说我疯之前,请阅读此 Wikipedia article 中的类似情况。)

那么 Joda 如何确保没有部分创建的格式化程序发布到这个静态变量中?

感谢您的解释!

返回

最佳答案

你说过,格式化程序是只读的。如果他们仅使用最终字段(我没有阅读格式化程序源代码),那么在 Java 语言规范的第 3 版中,他们会受到“最终字段语义”的部分对象创建的保护。我没有检查第 2 个 JSL 版本,也不确定该版本中的初始化是否正确。

查看 JLS 中的第 17.5 和 17.5.1 章。我将为所需的发生前关系构建一个“事件链”。

首先,在构造函数的某处有一个写入格式化程序中的最终字段。就是写w。当构造函数完成时,将执行“卡住”操作。我们称它为f。在程序顺序后面的某个地方(从构造函数返回后,可能是其他一些方法并从 toFormatter 返回)有一个写入 dt 字段。让我们给这个写一个名字。此写入 (a) 在“程序顺序”(单线程执行顺序)中的卡住操作 (f) 之后,因此 f happens-before a (hb(f, a)) 只是根据 JLS 定义。呼,初始化完成...:)

有时,在另一个线程中,会调用 dateTime().format。那时我们需要两次读取。两者中的第一个是读取格式化程序对象中的最终变量。我们称它为 r2(为了与 JLS 一致)。两者中的第二个是格式化程序的“this”读取。在读取 dt 字段时调用 dateTime() 方法期间会发生这种情况。我们称其为 read r1。我们现在有什么?读取 r1 看到一些写入 dt。我认为写入是上一段中的操作 a(为了简单起见,只有一个线程写入该字段)。由于 r1 看到写 a,则有 mc(a, r1)(“内存链”关系,第一个子句定义)。当前线程没有初始化格式化程序,在操作 r2 中读取它的字段并看到在操作 r1 中读取的格式化程序的“地址”。因此,根据定义,有一个 dereferences(r1, r2)(另一个从 JLS 排序的 Action )。

我们在卡住前写入,hb(w, f)。我们在分配 dt 之前卡住,hb(f, a)。我们读取了 dt, mc(a, r1)。我们在 r1 和 r2 之间有一个解引用链,dereferences(r1, r2)。根据 JLS 的定义,所有这些都会导致 hb(w, r2) 发生之前的关系。此外,根据定义,hb(d, w) 其中 d 是对象中最终字段的默认值写入。因此,读取 r2 看不到写入 w 而必须看到写入 r2(程序代码中唯一对该字段的写入)。

更多间接字段访问的顺序相同(存储在最终字段中的对象的最终字段,等等)。

但这还不是全部!无法访问部分构造的对象。但是还有一个更有趣的错误。在缺少任何显式同步的情况下,dateTime() 可能会返回 null。我不认为在实践中可以观察到这种行为,但 JLS 第 3 版不会阻止这种行为。第一次读取方法中的 dt 字段可能会看到另一个线程初始化的值,但第二次读取 dt 会看到“写入默认值”。没有 happens-before 关系可以阻止它。这种可能的行为特定于第 3 版,第 2 版有“写入主内存”/“从主内存读取”,这不允许线程看到及时返回的变量值。

关于java - 部分构造的对象/多线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2509847/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com