ret*_*eto 5 java multithreading jodatime
由于它在多线程方面的良好声誉,我使用的是joda.通过使所有Date/Time/DateTime对象不可变,可以使多线程日期处理变得高效.
但在这种情况下,我不确定Joda是否真的做得对.它可能会,但我很有兴趣看到一个解释.
当调用DateTime的toString()时,Joda会执行以下操作:
/* org.joda.time.base.AbstractInstant */
public String toString() {
return ISODateTimeFormat.dateTime().print(this);
}
Run Code Online (Sandbox Code Playgroud)
所有格式化程序都是线程安全的(它们也是不可变的),但是有关formatter-factory的内容:
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;
}
Run Code Online (Sandbox Code Playgroud)
这是单线程应用程序中的常见模式,但已知它在多线程环境中容易出错.
我看到以下危险:
没有问题,因为这只是一个辅助对象(与正常的单例模式情况不同),一个被保存在dt中,另一个被丢失并且迟早会被垃圾收集.
(在叫我疯了之前,请阅读本维基百科文章中的类似情况.)
那么Joda如何确保在这个静态变量中没有发布部分创建的格式化程序?
谢谢你的解释!
雷托
小智 4
您说过,格式化程序是只读的。如果它们仅使用最终字段(我没有阅读格式化程序源代码),那么在 Java 语言规范的第三版中,它们将受到“最终字段语义”的保护,免受部分对象创建的影响。我没有检查第二个 JSL 版本,也不确定这种初始化在该版本中是否正确。
请参阅 JLS 中的第 17.5 章和第 17.5.1 章。我将为所需的发生之前关系构建一个“事件链”。
首先,在构造函数中的某个地方有一个对格式化程序中的最终字段的写入。这是写w。当构造函数完成时,会发生“冻结”操作。我们称之为f。在程序顺序稍后的某个地方(从构造函数返回之后,可能是其他一些方法并从 toFormatter 返回),有一个对 dt 字段的写入。我们给这个写一个名字a。此写入 (a) 在“程序顺序”(单线程执行中的顺序)中的冻结操作 (f) 之后,因此根据 JLS 定义,f 发生在 a (hb(f, a)) 之前。唷,初始化完成...:)
有时,在另一个线程中,会调用 dateTime().format。这时候我们需要两次阅读。两者中的第一个是读取格式化程序对象中的最终变量。我们将其称为 r2(与 JLS 保持一致)。两者中的第二个是读取格式化程序的“this”。当读取 dt 字段时调用 dateTime() 方法期间会发生这种情况。我们将此称为读取 r1。我们现在有什么?读取 r1 看到对 dt 进行了一些写入。我认为该写入是上一段中的操作 a (为了简单起见,只有一个线程写入该字段)。当r1看到写入a时,则有mc(a, r1)(“记忆链”关系,第一个子句定义)。当前线程没有初始化格式化程序,在操作 r2 中读取它的字段,并看到在操作 r1 中读取的格式化程序的“地址”。因此,根据定义,存在取消引用(r1,r2)(来自 JLS 的另一个操作排序)。
我们在冻结之前已经写了 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 第三版并没有阻止这种行为。方法中第一次读取 dt 字段可能会看到由另一个线程初始化的值,但第二次读取 dt 可以看到“写入默认值”。不存在事前发生关系来阻止它。这种可能的行为是第三版特有的,第二版有“写入主内存”/“从主内存读取”,它不允许线程看到及时返回的变量值。