【问题标题】:How to make a static Calendar thread safe如何使静态日历线程安全
【发布时间】:2011-09-08 20:36:18
【问题描述】:

我想为一些静态方法使用日历并使用静态字段:

private static Calendar calendar = Calendar.getInstance();

现在我读到 java.util.Calendar 不是线程安全的。我怎样才能使这个线程安全(它应该是静态)?

【问题讨论】:

  • 你的静态日历只是固定日期吗?
  • 也许让它“易变”会有所帮助?
  • 不,我只是不想为每次调用我的方法创建这么多实例,因为这可能需要时间。
  • 您的意思是担心对构造函数的多次调用会影响性能?
  • 我开始得出结论,不应将日历视为简单的“非线程安全”。它应该被标记为“线程敌对”。

标签: java static calendar thread-safety


【解决方案1】:

在方法中创建一个Calendar 作为局部变量。如果您需要跨方法使用相同的日历,您可能会在更适合使用(单例或准单例)对象的情况下使用静态方法。

【讨论】:

  • 您是否真的建议将一个可变的、非线程安全的对象单例化将有助于使其类线程安全?真的吗?
【解决方案2】:

如果它不是线程安全的,你就不能做一些线程安全的东西。对于Calendar,即使从它读取数据也不是线程安全的,因为它可以更新内部数据结构。

如果可能的话,我建议改用Joda Time

  • 大多数类型都是不可变的
  • 不可变类型是线程安全的
  • 无论如何,它通常是一个更好的 API

如果您绝对必须使用Calendar,您可以创建一个锁定对象并通过一个锁定来设置所有访问。例如:

private static final Calendar calendar = Calendar.getInstance();
private static final Object calendarLock = new Object();

public static int getYear()
{
    synchronized(calendarLock)
    {
        return calendar.get(Calendar.YEAR);
    }
}

// Ditto for other methods

虽然很恶心。您可以只有 一个 同步方法,该方法在每次需要时创建原始日历的克隆,当然...通过调用 computeFieldscomputeTime 您可以使 后续当然,读操作是线程安全的,但我个人不愿意尝试它。

【讨论】:

  • 如果你克隆一个模板,你不需要同步块。因为模板本身是不可变的。
  • 即使读取日历会更新内部状态,您能否举例说明两个线程何时可能尝试为该状态设置不同的值?
  • @bestsss:嗯,这取决于克隆本身是否是线程安全的。鉴于 Calendar 的其余部分缺乏线程安全性,我不想依赖它。
  • @Peter:想象一下,如果 areFieldsSet 在计算字段值本身变得可见之前由于另一个线程中的计算而对一个线程可见true。一个线程可能会读取 some 旧字段值和 some 新字段值,从而导致奇怪的结果。我不喜欢尝试推理实现细节来决定这是否可能。
  • @Voo:我的闹钟可以设置为每天当地时间8点叫醒我,无论我身在何处。应该在其表示中使用哪个时区? ;)
【解决方案3】:

你不能。是的,您可以对其进行同步,但它仍然具有可变状态字段。您必须创建自己的日历对象。

如果可能,请使用轻量级的东西,例如以毫秒为单位测量时间,并且仅在需要时转换为日历。

【讨论】:

    【解决方案4】:

    只要您不更改日历,它就是线程安全的。您的示例中的用法很好。

    值得注意的是,Calendar 不是一个高效的类,您应该只将它用于复杂的操作(例如查找下个月/下一年)恕我直言:如果您确实将它用于复杂的操作,请仅使用局部变量。

    如果您只想要时间快照,更快的方法是使用 currentTimeMillis,它甚至可以创建一个对象。如果你想让它成为线程安全的,你可以让这个字段变得易变。

    private static long now = System.currentTimeMillis();
    

    用法有点可疑。你为什么要获取当前时间并像这样全局存储它。这让我想起了一个老笑话。

    - 你有时间吗?
    - 是的,我已经把它写在某个地方了。

    【讨论】:

    • “改变它”到底是什么意思,记住从它读取可以改变它的内部状态?如果发现两个同时读取操作可能可能最终破坏状态,我不会感到完全惊讶。这不太可能,但我不想依赖它。日历是一个讨厌的,讨厌的类:(
    • 读取它不会以不一致的方式改变它的状态。如果您担心这一点,您将不得不对 String.hashCode() 说同样的话。
    • 我同意日历很讨厌。你试过序列化它吗?它发送整个 TimeZone 信息,这对性能来说不是很好,但这意味着如果您的系统具有最新的 TimeZone 信息,但是您阅读旧日历或从不是最新的系统获取日历,您会变得微妙由于您未运行的代码中的错误,您从未想过会发生 TimeZone 问题。 :P
    • 在我的回答中查看我回复您的评论。 String 被设计为线程安全的,因此它的哈希缓存是线程安全的:值从“未缓存”原子地更改为“缓存值”。与Calendar 相比,boolean 字段确定是否设置了字段,并且字段本身是独立的。什么可以保证对这组数据的所有更改都以原子方式或以理想的顺序发生?
    • @scottb Calendar 变慢的部分原因是创建 TimeZone 和 Locale 对象。如果您缓存这些不可变对象,您可以更快地创建新日历。为了使日历线程安全,您需要在使用时锁定它。
    猜你喜欢
    • 2013-05-08
    • 1970-01-01
    • 1970-01-01
    • 2014-09-16
    • 1970-01-01
    • 2012-09-22
    • 2015-03-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多