SimpleDateFormat类的parse和format方法的线程安全问题
一. 现象
-
使用
SimpleDateFormat#parse
方法,可以将满足格式要求的字符串转换成Date对象public static void main(String[] args){ String dataTime = "20221025210000"; SimpleDateFormat YMDHMS = new SimpleDateFormat("yyyyMMddHHmmss"); Date date = YMDHMS.parse(dataTime); }
-
使用
SimpleDateFormat#format
方法,可以将Date类型的对象转换成一定格式的字符串public static void main(String[] args){ Date date = new Date(); SimpleDateFormat YMDHMS = new SimpleDateFormat("yyyyMMddHHmmss"); String dataTime = YMDHMS.format(date) }
但是在并发情况下会报java.lang.NumberFormatException 异常
二. 解决方法
问题的根源就是:SimpleDateFormat 被多个线程共享,它的
parse
和format
方法就像是 i++ 一样,它维持的Calender
等属性无法保证原子性。
-
使用局部变量,保证每个线程中都有一份SimpleDateFormat实例,那么,线程之间的 SimpleDateFormat实例 就没有任何关系了,不会互相影响了。
不过,也加重了创建对象的负担,会频繁地创建和销毁对象,效率较低。 -
使用 ThreadLocal实现 每个线程都可以得到单独的一个SimpleDateFormat的实例,那么自然也就不存在竞争问题了
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static Date parse(String dateStr) throws ParseException { return threadLocal.get().parse(dateStr); } public static String format(Date date) { return threadLocal.get().format(date); }
-
同步代码块 synchronized
/** * 返回一个ThreadLocal的sdf,每个线程只会new一次sdf * * @param pattern * @return */ public static SimpleDateFormat getSdf(final String pattern) { ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern); // 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdf if (tl == null) { synchronized (lockObj) { tl = sdfMap.get(pattern); if (tl == null) { // 这里是关键,使用ThreadLocal<SimpleDateFormat>替代原来直接new SimpleDateFormat tl = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat(pattern); } }; sdfMap.put(pattern, tl); } } } return tl.get(); }
-
Lock锁方式,与synchronized锁方式实现原理相同,都是在高并发下通过JVM的锁机制来保证程序的线程安全。具体使用的是 Lock 的子类
ReentrantLock
(可重入锁) -
基于JDK1.8的 DateTimeFormatter
// 指定格式 静态方法 ofPattern() DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // DateTimeFormatter 自带的格式方法 LocalDateTime now = LocalDateTime.now(); // DateTimeFormatter 把日期对象,格式化成字符串 String strDate1 = formatter.format(now);
三. 线程安全的类LocalDateTime使用
-
LocalDateTime 和Date转换
//将java8 的 java.time.LocalDateTime 转换为 java.util.Date,默认时区为东8区 public Date localDateTimeConvertToDate(LocalDateTime localDateTime) { return Date.from(localDateTime.toInstant(ZoneOffset.of("+8"))); }
-
获取指定时间的前一天
DateTimeFormatter YMDHMS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); public Date getPreDateForYMDHMS(String dataTime) throws ParseException { // String dataTime = "20221025210000" LocalDateTime localDateTime=LocalDateTime.parse(dataTime,YMDHMS); Date date = localDateTimeConvertToDate(localDateTime); Calendar c = Calendar.getInstance(); c.setTime(date); c.add(Calendar.DAY_OF_MONTH, -1); return c.getTime(); }
-
获取指定时间的时间戳(毫秒)
DateTimeFormatter YMDHMS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); public long getDateTime(String dataTime) throws ParseException { LocalDateTime localDateTime=LocalDateTime.parse(dataTime,YMDHMS); long time = localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli(); return time; }
-
获取当前日期
public String formatDateYmd(){ LocalDateTime now = LocalDateTime.now(); String strDate = YMD.format(now); return strDate; }