1

我有一个包含许多表的数据库,这些表以 UTC 数字格式(修改后的儒略日数字)存储日期/时间。

这很好用,但是当然任何时间日期都需要显示给用户,日期/时间必须从 UTC 数字格式转换为本地化的字符串格式。因为 JDK 日历 API 提供了历史上准确的夏令时和时区偏移,所以我一直使用公历来实现这个唯一目的。我只使用它来将时间戳从 UTC 映射到本地时间。

因为我有很多表需要执行这种转换,而且 Calendar 对象的创建成本很高,所以我只在语言环境时区更改时才创建新的 GregorianCalendar 对象。我一直将当前日历保存在 JulianDay 类的静态成员字段中(这是提供我用来将 UTC 提供到本地映射的函数的类)。以下是 JulianDay 的字段:

public final class JulianDay {

    private static final int YEAR = 0;
    private static final int MONTH = 1;
    private static final int DAY = 2;
    private static final int HOURS = 3;
    private static final int MINUTES = 4;
    private static final int SECONDS = 5;
    private static final int MILLIS = 6;

    public  static final double POSIX_EPOCH_MJD = 40587.0;
    private static final String TIME_ZONE_UTC   = "UTC";
    private static final GregorianCalendar CAL_UTC = 
            new GregorianCalendar(TimeZone.getTimeZone(TIME_ZONE_UTC));

    private static GregorianCalendar c_selCal = null;

    public static void setCurrentCalendar(String tzid) 
            { JulianDay.c_selCal = JulianDay.findCalendarForTimeZone(tzid); }

    public static GregorianCalendar getCurrentCalendar() 
            { return JulianDay.c_selCal; }
    :
    :
}

以上是在一个单线程的客户端 GUI 应用程序中,但我很快需要开发一个服务器端的 Web 应用程序,它将使用这个 API 的大部分。我需要弄清楚如何保留我对 JDK 日历 API 的投资,并以某种方式保证我的应用程序线程安全。Joda time 是一个选项,但如果可能的话,我不想添加该依赖项(无论如何,我使用 Java API 只是对时区数据库的 easymode 访问)。

由于日历显然是线程敌对的,我不确定如何从这里开始。

我的想法是,由于 JulianDay 类构造了 GregorianCalendar 的所有实例(来自时区 id 字符串),我可以重构我的代码,以便我可以消除getCurrentCalendar()发布日历引用的方法。

如果我可以限制GregorianCalendar对 within的所有访问JulianDay,我是否可以安全地假设,即使它是一个危险的可变类,我的应用程序也是线程安全的?即使在我的课堂上,我仍然需要将访问GregorianCalendar与锁定对象同步JulianDay,对吗?

4

3 回答 3

3

您可以使用 Thread 本地静态成员变量,例如:

private static final ThreadLocal<GregorianCalendar> CAL_UTC = 
        new ThreadLocal<GregorianCalendar>() {
         @Override protected GregorianCalendar initialValue() {
             return  new GregorianCalendar(TimeZone.getTimeZone(TIME_ZONE_UTC));
     }
 };

这样每个线程都有一个对象。查看ORACLE 文档了解更多信息。

希望这会有所帮助......干杯!

ps:使用这种方法有两个方面需要考虑:

  1. 线程局部变量的目的是为不同的线程具有不同的值。如果您需要线程使用相同的日历,这种方法可能不适合您。
  2. 请注意您的日历不会被意外垃圾收集(请参阅ThreadLocal Resource Leak 和 WeakReference) - 因此请保持对ThreadLocal实例的强引用。

如果您需要线程访问实例,则必须派生自己的类并同步相关方法或寻找替代实现(检查实例 Apache commons)。

于 2013-06-06T18:17:51.453 回答
1

如果正确同步,可变实例可以是线程安全的。不可变实例的优势在于您不需要同步。所以是的,如果您将所有变异更改同步到日历,您可以继续使用它。

但!服务器端应用程序与多线程客户端应用程序完全不同。单点同步是阻止应用程序的可靠方法 - 根据负载,连接可能会停止。虽然 ThreadLocal 可能会缓解同步问题,但由于每个 Calendar 仅在单个线程中可见,请考虑线程重用。

在典型的应用服务器环境中,相同的线程不仅会被连续的用户重用,而且还会在同一个会话中重用——为了控制和保存服务器资源,每个 GET 都是一个工作单元,一旦工作,单个线程就可以为其提供服务单元完成后,同一个线程可以为另一个用户获取另一个工作单元。因此,如果日历是可变的并且必须根据特定用户进行更改,您最终将不得不在每次需要时进行设置。所以实际上根本没有缓存效果!

对于应用服务器环境中的此类服务(转换日期的服务),有(我说“是”,因为我们几年前曾经这样做过)无状态和有状态 bean,现在我认为缓存服务可能更合适,每个时区填充多个实例。

只要实例是可变的,它们就必须在缓存中被锁定(可能会被删除并重新插入)。并且必须管理缓存、适当调整大小等。

最好永远保持无国籍状态——就像乔达一样。并预填充缓存以避免在每条记录上动态创建新对象(取决于规模另一种降低应用服务器速度的方法)。

于 2013-06-07T07:52:17.113 回答
0

作为解决方案怎么样?:

目前,JulianDay 是一个只有静态成员的类(所有方法都是静态的),我没有创建 JulianDay 的实例。

我建议像这样重构 JulianDay:

  • 将 JulianDay 重新用于可实例化的类
  • 使 JulianDay 的实例成为 ThreadLocal
  • 使 JulianDay 的实例不可变
  • 对 JulianDay 构造函数使用单个字符串参数(日历的时区 ID)
  • 让 JulianDay 封装并限制GregorianCalendar
  • 在内部同步对 GregorianCalendar 的访问,以便使用是原子的

我相信如果按照上述方式开发 JulianDay 的实例将是线程安全的,最好的部分是我不需要对现有代码进行太多重构即可使其与可实例化的 JulianDay 实例一起使用。

关键部分是不让 GregorianCalendar 逃脱。

于 2013-06-08T16:19:31.403 回答