此代码是否需要嵌套锁?

Is nesting of locks necessary for this code?

我在 class 中有两个共享可变对象,它们的线程安全策略已定义为 "thread safe."

public static final GregorianCalendar CAL = new GregorianCalendar();
public static final SimpleDateFormat  SDF = new SimpleDateFormat();

目的是减少创建对象的次数,因为创建这些对象的成本很高,而且需要使用它们的方法预计会被频繁调用。

这是一个这样的(静态工厂)方法:

    public static MJD ofTimeStampInZone(String stamp, String form, TimeZone tz) {

        double result;

        synchronized(lockCal) {
            synchronized(lockSdf) {
                CAL.setTimeZone(tz);
                SDF.setCalendar(CAL);
                SDF.applyPattern(form);

                try {
                    Date d = SDF.parse(stamp);
                    CAL.setTime(d);
                    result = (CAL.getTimeInMillis() / (86400.0 * 1000.0)) + 
                            POSIX_EPOCH_AS_MJD;
                } 
                catch (ParseException e) 
                    { throw new IllegalArgumentException("Invalid parsing format"); }
            }
        }
        return new MJD(result);
    }

我还为此 class 设置了一个策略,即必须始终在 lockSdf 之前获取 lockCal。然而,这也是如此 class:

因为 SDF 依赖于 CAL,我想知道单独锁定 lockCal 是否足以防止并发访问期间的数据不一致。这将允许我免除对 SDF 的锁定。换句话说,如果我只使用:

,有上面的条件,是否仍然保证线程安全
    public static MJD ofTimeStampInZone(String stamp, String form, TimeZone tz) {

        double result;

        synchronized(lockCal) {
                CAL.setTimeZone(tz);
                SDF.setCalendar(CAL);
                SDF.applyPattern(form);

                try {
                    Date d = SDF.parse(stamp);
                    CAL.setTime(d);
                    result = (CAL.getTimeInMillis() / (86400.0 * 1000.0)) + 
                            POSIX_EPOCH_AS_MJD;
                } 
                catch (ParseException e) 
                    { throw new IllegalArgumentException("Invalid parsing format"); }
        }
        return new MJD(result);
    }

一般来说,这里不保证线程安全。假设 SDF 应该由 lockSDF 保护,但它在不同的锁下被修改,如果另一个线程仅获取 lockSDF,它可能看不到 SDF 更改的结果。 但是您有一个策略:在 lockSdf 之前获取 lockCal。看起来 "kind of" 解决了问题,但是

1) 这使得关于线程安全的推理变得过于困难

2) 它使 lockSdf 无用

假设 SDF 依赖于 CAL 并且 CAL 由 lockCal 保护,那么也可以使用 lockCal 保护 SDF。

Java 并发实践(Brian Goetz):

第 1 部分总结

...

Guard all variables in an invariant with the same lock.

...

如果 SDF 只被一个已经获取 lockCal 的线程使用,它一次只能被一个线程访问,即即使你删除它也是线程安全的lockSdf.

上的锁

如果您选择依赖这个观察结果,您应该清楚地记录下来,这样未来的维护程序员就不会在 synchronized (lockCal).

之外开始使用 SDF