【问题标题】:Is nesting of locks necessary for this code?这段代码是否需要嵌套锁?
【发布时间】:2015-10-29 02:03:15
【问题描述】:

我在一个类中有两个共享可变对象,其线程安全策略已被定义为“线程安全”。

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。然而,这个类也是如此:

  • 可以在没有 SDF 的情况下锁定和使用 CAL,在这种情况下,SDF 不会被锁定。
  • 除非同时使用 CAL,否则绝不会在方法中使用 SDF

因为 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 上使用双重锁定吗?
  • 是的,我还有另外两种方法可以锁定lockCal,然后锁定lockSdf。我有几种方法只锁定lockCal,但这些方法不会改变SDF的状态
  • 请贴出相关代码sn-ps。您不需要向我们展示所有内容,只需类似于您向我们展示的内容ofTimeStampInZone
  • 其线程安全策略已被定义为“线程安全”。 - 但这些类不是线程安全的:stackoverflow.com/questions/12131324/…docs.oracle.com/javase/7/docs/api/java/text/…
  • @Ruslan:不要求线程安全的类不能由本身不是线程安全的组件组成(实际上,线程安全的类是由可变的、非线程的安全类(例如 ArrayList)一直)。

标签: java concurrency synchronization thread-safety


【解决方案1】:

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

1) 线程安全的推理太难了

2) 它使 lockSdf 无用

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

Java 并发实践(Brian Goetz):

第 1 部分总结

...

用同一个锁保护一个不变量中的所有变量。

...

【讨论】:

  • 我不确定我是否理解您的回答。当由 lockCal 保护时,您对 SDF 的潜在可见性问题提出了重要的一点。但是你是说如果 SDF 依赖于 CAL,那么用同一个锁锁定两个可变对象是可以接受的吗?
  • @scottb,是的,这是可以接受的,实际上这是推荐的方式。我用引用更新了我的答案。
【解决方案2】:

如果SDF 只被一个已经获得lockCal 的线程使用,那么它一次只能被一个线程访问,即即使你取消lockSdf 上的锁它也是线程安全的.

如果您选择依赖此观察结果,您应该清楚地记录它,这样以后的维护程序员就不会在 synchronized (lockCal) 之外开始使用 SDF

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-21
    • 2015-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多