Ren*_*nee 3 java static multithreading calendar thread-safety
我有一个cron工作来清除超过某个月数(由用户设置)的数据.cron作业调用一个purgeData()方法来清除postgres表中的数据.我Calendar从当前日期(via GregorianCalendar.getInstance)操作java 来确定删除之前数据的目标日期.
我的问题是日历操作和/或将新操作的日历转换为字符串以便在postgres中使用偶尔会失败,将目标日期设置为删除当前日期之前的所有内容的当前日期,而不是早于1的数据(或#数月保持数月).
这是我简单的日期格式:
public static final SimpleDateFormat dateFormatter = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss.SSS");
Run Code Online (Sandbox Code Playgroud)
这是我的方法:
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;
}
Run Code Online (Sandbox Code Playgroud)
日期失败似乎是随机的,因为在一次部署成功时,它会在另一次部署失败.
失败输出:
20150421-00:33:11.006 Postgres通知 - 清除:#保存数据的月份:1目标日期(字符串):2015-04-21 00:00:00.001,开始月份:2,当前月份:3成功:先前清除数据至2015-04-21 00:00:00.001#rows deleted:7575704(#rows deleted last purge: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删除最后净化:128570)
注意:之前的清除数据应该是2015-03-14 20:30:00.000(请注意,由于cron作业从00:30开始每4小时运行一次,因此也是1小时30分钟)
成功输出: 20150421-00:30:02.559 Postgres通知 - 清除:#个月保存数据:1目标日期(字符串):2015-03-21 00:30:00.003,开始月份:2,当前月份:3成功: 2015-03-21 00:30:00.003之前的清除数据#rows删除:139757(#rows删除最后清除:33344)
似乎日期操作实际上有效,如开始月份和当前月份的输出所示.在两种故障情况下,整数值都不同.但是,将字符串转换为SimpleDateFormat似乎是错误的.
我已经阅读了javadocs,因为在Calendar上设置字段时,必须调用get()来重新计算时间.但是,add()应该强制重新计算.
你的dateformatter不是线程安全的,将它存储在一个类变量中,让多个线程同时敲击它会给你带来无效的结果.
这在SimpleDateFormat的API文档中记录,标题为Synchronization:
日期格式未同步.建议为每个线程创建单独的格式实例.如果多个线程同时访问格式,则必须在外部进行同步.
一个解决方法是让您的方法创建自己的SimpleDateFormatter实例.还有更多选项,在相关问题中列出:制作DateFormat Threadsafe.使用什么,同步或线程本地.
但是,不需要格式化日期,而是可以将日期作为参数传递给PreparedStatement:
sqlString = "DELETE FROM \"" + tableName + "\" WHERE datetime < ?";
pst = con.prepareStatement(sqlString);
pst.setTimestamp(1, new Timestamp(startDate.getTime()));
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
115 次 |
| 最近记录: |