这段代码需要嵌套锁定吗?

3

我在一个被定义为"线程安全"的类中有两个共享的可变对象。

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);
    }

我也为这个类设置了一个规则,即必须在lockSdf之前获取lockCal。然而,对于这个类来说,以下情况也是正确的:
  • 可以锁定并使用CAL而不使用SDF,在这种情况下,SDF没有被锁定。
  • SDF在方法中从未被使用,除非也使用了CAL。
因为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);
    }

你有没有其他使用双重锁定在 lockCallockSdf 上的代码? - Tim Biegeleisen
是的,我有另外两种方法,它们会锁定lockCallockSdf。我还有几种只锁定lockCal的方法,但这些方法不会改变SDF的状态。 - scottb
请发布相关的代码片段。您不需要展示所有内容,只需展示类似于您为 ofTimeStampInZone 展示的内容即可。 - Tim Biegeleisen
其线程安全策略被定义为“线程安全”,但这些类不是线程安全的:https://dev59.com/WWct5IYBdhLWcg3wZMfn 和 http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html - Ruslan
1
@Ruslan:没有要求一个线程安全的类不能由本身不是线程安全的组件组成(事实上,线程安全的类经常由可变的、非线程安全的类(例如ArrayList)组成)。 - scottb
@scottb,是的。我错误地将其阅读为“GregorianCalendar和SimpleDateFormat是线程安全的”。对于造成的混淆,我感到抱歉。 - Ruslan
2个回答

1
如果SDF只被已经获取了lockCal的线程使用,那么它只能被一个线程访问,即使你删除了对lockSdf的锁定,它也是线程安全的。
如果你选择依赖这个观察结果,你应该清楚地记录它,以便未来的维护程序员不会在synchronized (lockCal)之外使用SDF

0

一般来说,这里不能保证线程安全。假设SDF应该由lockSDF保护,但是如果在另一个锁下修改了它,那么如果只获取lockSDF,另一个线程可能看不到SDF的更改结果。

但是你有一个策略:在获取lockSdf之前获取lockCal。看起来“有点”解决了问题,但是:

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

2)这使得lockSdf无用。

假设SDF依赖于CAL,并且CAL由lockCal保护,那么使用lockCal来保护SDF也是有意义的。

《Java并发编程实战》(Brian Goetz):

第1部分总结

...

使用相同的锁来保护不变量中的所有变量。

...


1
我不确定我理解您的答案。您提出了关于当lockCal保护SDF时可能存在可见性问题的重要观点。但是,您是否在说,如果SDF依赖于CAL,则使用相同的锁锁定两个可变对象是否可以接受? - scottb
@scottb,是的,这是可以接受的,实际上这是推荐的方式。我在我的回答中加入了一句引用。 - Ruslan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接