背景:项目上多线程场景下,出现个别数据日期格式解析错误的情况。
原因:
SimpleDateFormat是线程不安全的类,SimpleDateFormat继承自DateFormat类,在 DataFormat 类内部有一个 Calendar 对象引用,SimpleDateFormat 转换日期都是靠这个 Calendar 对象来操作的,Calendar 在用的时候是直接使用的,而且是改变了 Calendar 的值,但是由于Calendar内部并没有线程安全机制,并且这两个操作也都不是原子性的,所以当多个线程同时操作一个SimpleDateFormat时就会引起Calendar对象的值混乱。类似地, format()方法也存在同样的问题。
如果 SimpleDateFormart 是静态的话,那么多个线程之间就会共享一个SimpleDateFormart,同时也会共享SimpleDateFormart里的Calendar引用,所以就会出现数据赋值覆盖情况。
解决方案:
1.每次使用创建一个新的SimpleDateFormat(不推荐使用,因为频繁地创建和销毁对象,效率低)
2.synchronized 锁(不推荐使用,因为使用了 synchronized 加锁后的多线程就相当于串行,线程阻塞,执行效率低)
3.ThreadLocal
ThreadLocal 提供了线程本地的实例,它与普通变量的区别在于,每个使用该线程变量的线程都会初始化一个完全独立的实例副本。
public class DateUtils {
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
public static Date parse(String formatPattern, String dateString) throws ParseException {
return threadLocal.get().parse(dateString);
}
public static String format(String formatPattern, Date date) {
return threadLocal.get().format(date);
}
}
4.如果是jdk8的项目,推荐使用DateTimeFormatter (Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat)
//将String类型的时间戳转换为指定格式的String类型的日期
String time = "1701052121257";
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
Instant instant = Instant.ofEpochMilli(Long.valueOf(time));
//ZoneId.of("GMT+8")设置时区为东八区,默认为ZoneId.systemDefault()
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant,ZoneId.of("GMT+8"));
String tranTime = (localDateTime.format(format));
//获取当前时间
LocalDateTime date = LocalDateTime.now();