以前只把SimpleDateFormat类当前一个简单的工具类使用,并没有注意它存在的线程安全问题,直到最近在近期一个数据迁移项目中才碰到。我的迁移程序会比较迁移前和后的数据是否一致,在做这个事情的时候,由于之前的数据库中存储的日期使用的14位字符串,即20140502112230,而新库中规范要求使用Timestamp类型,这自然要涉及到日期类型与字符串之间的转化。因为日期格式固定,因此我将这个方法封装在了一个工具类中,并将SimpleDateFormat类的变量声明成了一个类变量,如下:
public class Util {
// 声明了一个静态变量
static final SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat("yyyyMMddHHmmss");
public static Timestamp str2TimeStamp(String datestr) {
try {
if (StringUtils.isBlank(datestr) || datestr.length() != 14) {
return null;
}
Date d = yyyyMMddHHmmss.parse(datestr);
return new Timestamp(d.getTime());
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
public static String timeStamp2String(Timestamp t) {
if (t == null) {
return null;
}
return yyyyMMddHHmmss.format(t);
}
}
在执行多线程的比较程序过程中,发现日期值老是错误,断点查看,明明字符串是20140502112230,Timestamp却出现了另外的值,才意识到SimpleDateFormat的线程安全问题,赶紧查看javadoc:
Synchronization
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.
意思是说,该日期格式类不是同步的。所以建议在使用的时候,为每个线程单独创建一个SimpleDateForm实例。如果非要多个线程并发地访问一个实例,那么必须使用额外的同步。
或者我们可以把相应的对象放到ThreadLocal中,多个线程调用时使用的都是各自线程中的副本,就不会出现线程安全问题了。
public static final ThreadLocal<SimpleDateFormat> sdfThread = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));