java - Java 日历操作的结果不一致

标签 java multithreading static calendar thread-safety

我有一个 cron 作业来清除早于特定月数(由用户设置)的数据。 cron 作业调用 purgeData() 方法从 postgres 表中清除数据。我从当前日期开始操作 java Calendar(通过 GregorianCalendar.getInstance)以确定删除数据之前的目标日期。

我的问题是日历操作和/或将新操作的日历转换为用于 postgres 的字符串偶尔会失败,将目标日期设置为当前日期,这会删除当前日期之前的所有内容,而不是早于 1 的数据(或#months 保留数据)一个月大。

这是我的简单日期格式:

public static final SimpleDateFormat dateFormatter = new SimpleDateFormat(
            "yyyy-MM-dd HH:mm:ss.SSS");

这是我的方法:

public String purgeData() throws ParseException {
    Connection con = null;
    String sqlString = "";
    PreparedStatement pst = null;
    String returnString = "";

    Calendar startDate = GregorianCalendar.getInstance();
    returnString += "# Months to keep data: " + getNumMonthsKeepData();
    startDate.add(Calendar.MONTH, getNumMonthsKeepData() * -1);
    String targetDate = dateFormatter.format(startDate.getTime());
    Calendar today = GregorianCalendar.getInstance();

    returnString +=" Target date (string): " + targetDate + " start date (Calendar): " + startDate.toString() + ", Start month: " + startDate.get(Calendar.MONTH) + ", Current month: " + today.get(Calendar.MONTH);

    if (startDate.get(Calendar.MONTH)!= today.get(Calendar.MONTH)) {

        String tableName = getPreviousMonthlyTable();
        try {
            con = getDBConnection();

            try {
                // Delete old data
                sqlString = "DELETE FROM \"" + tableName
                        + "\" WHERE  datetime < '" + targetDate + "'";

                pst = con.prepareStatement(sqlString);
                int rowsDeleted = pst.executeUpdate();
                returnString += "SUCCESS: Purged data prior to " + targetDate
                        + " # rows deleted: " + rowsDeleted
                        + "( # rows deleted last purge: "
                        + numRowsDeletedPreviously + " )\n";

            } catch (SQLException ex) {
                returnString += "FAILED to execute: " + sqlString;
            }

            try {
                if (pst != null) {
                    pst.close();
                }
                if (con != null) {
                    con.close();
                }

            } catch (SQLException ex) {
                return null;
            }
        } catch (SQLException ex) {
            returnString += "Delete from table fail: " + ex.getMessage();
        }
    } else {
        returnString += "FAIL:  Fail to delete data prior to: " + targetDate + ". Start month: " + startDate.get(Calendar.MONTH)
                + " equals current month: " + today.get(Calendar.MONTH);
    }
    return returnString;
}

日期失败似乎是随机的,因为当它在一个部署上成功时,它在另一个部署上失败。

失败输出:

20150421-00:33:11.006 Postgres 通知 - 清除:# 保留数据的月份:1 目标日期(字符串):2015-04-21 00:00:00.001/strong>,开始月份:2,当前月份:3 成功:在 2015-04-21 00:00:00.001 之前清除数据 # 删除的行数:7575704(上次清除删除的行数:26608)

注意:目标日期应为 2015-03-21 00:00:30.000(请注意,由于 cron 作业从 00:30 开始每 4 小时运行一次,因此也有 30 分钟的休息时间)

较旧的故障输出(在添加更多日志记录之前): 20150414-20:37:53.347 Postgres 通知 - 清除:成功:在 2015-04-14 19:00:00.004 之前清除数据 # rows deleted: 12195291( # rows deleted last purge: 128570 )

注意:清除之前的数据应该是 2015-03-14 20:30:00.000(请注意,它也是 1 小时 30 分钟,因为 cron 作业从 00 开始每 4 小时运行一次:30)

成功输出: 20150421-00:30:02.559 Postgres 通知 - 清除:# 保留数据的月份:1 目标日期(字符串):2015-03-21 00:30:00.003,开始月份:2,当前月份:3 成功:在 2015-03-21 00:30:00.003 之前清除数据 # 删除的行数:139757(上次清除删除的行数:33344)

日期操作似乎确实有效,如开始月份和当前月份的输出所示。在这两种失败情况下,整数值都不同。但是,字符串转换为 SimpleDateFormat 似乎是错误的。

我已经阅读了 javadocs,其中在日历上设置字段时,必须调用 get() 才能重新计算时间。但是,add() 应该强制重新计算。

最佳答案

您的 dateformatter 不是线程安全的,将其存储在类变量中并让多个线程同时对其进行锤击会给您带来无效的结果。

这记录在 API documentation for SimpleDateFormat 中在同步标题下:

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

一个解决方法是让您的方法创建自己的 SimpleDateFormatter 实例。还有更多选项,在此处的相关问题中列出:Making DateFormat Threadsafe. What to use, synchronized or Thread local .

但是,格式化日期不是必需的,您可以将日期作为参数传递给 PreparedStatement:

sqlString = "DELETE FROM \"" + tableName + "\" WHERE datetime < ?";
pst = con.prepareStatement(sqlString);
pst.setTimestamp(1, new Timestamp(startDate.getTime()));

关于java - Java 日历操作的结果不一致,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29781765/

相关文章:

java - java中可以有多个调度线程池吗?

java - Android VM 是否可以在不终止整个 Android 应用程序的情况下垃圾收集静态变量?

c# - 返回从未使用过的对象的构造函数是否浪费?

java - SonarQube自定义规则-获取方法参数所有者类

java - 使用 HTTPHandler 上传文件

java - 如何拦截 session 结束并获取属性?

multithreading - 同一节点中MPI进程如何通信

java - 访问传递给 jasper 记录的唯一 Java bean

python - 是否有多线程 map() 函数?

python - Django - 在模板中引用静态文件