在进行日期的格式处理时,我们一般习惯于使用Date,同时用simpleDateFormat进行格式化。通常我们会在类中定义一个全局静态终态(final static)的SimpleDateFormat对象对所有方法中可能用到的日期进行处理,但是在多个线程并发执行的情况下,这样是存在问题的。
暴露问题
如下的代码模拟了多线程情况下对日期的解析情况,在每个线程的最后,比较了解析之后的年份和传入的年份是否相同。
public class dateTest {
private static final SimpleDateFormat format =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final Executor threadPool =
new ThreadPoolExecutor(50,50, 30, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(10),
new ThreadFactory() {
private final AtomicInteger order = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "thread-"+ order.getAndIncrement());
}
});
public static void main(String[] args) {
for (int year = 120; year < 180; year++) {
Date date = new Date(year, 1, 1);
threadPool.execute(() -> {
String format1 = format.format(date);
System.out.println(Thread.currentThread().getName() + ",actualYear=" + format1.split("-")[0] + ", expectYear="+ (date.getYear()+1900) + ", result"+
format1.split("-")[0].equals(String.valueOf(date.getYear()+1900)));
});
}
}
}
运行结果:
在多线程的情况下,存在问题,存在解析后的时间和解析之前的时间年限对不上的问题。这是为什么呢?
分析原因
如下是SimpleDateFormater的format方法,对于上面的示例,simpleDateFormat对象全局只有一个,但是在第四行,simpleDateFormat中的calendar对象发生了变化,且在第27行的方法内部,calendar参与构建format之后的结果,也就是toAppendTo。
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
boolean useDateFormatSymbols = useDateFormatSymbols();
for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
}
switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;
case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break;
default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}
示意图:
执行分析:
- 线程一执行完calendar.setTime,失去时间片
- 线程二执行完calandar.setTime,失去时间片
- 线程一继续执行 ,此时解析出的时间和传入的时间发生了偏差
替代方法
使用Java8中的时间相关API。
日期时间 API 是 Java 8 版本的最大特性之一。 Java 从一开始就缺少一致的日期和时间方法,Java 8 的核心 API 增加了对日期,时间,时间戳,时区等相关内容的完整补充,相关的类放在java.time.*这个包下:
新的时间相关API具有以下的特点:
不可变性: 新的时间API中的所有类都是不可变的,适用于多线程环境
概念分明: 单独定义了日期localDate, 时间LocalTime,时器&时间LocalDateTime,时间戳Instant,时区,Duration,Period等类。
所以,针对上面的问题,使用新的API就可以迎刃而解了~
LocalDate date = LocalDate.of(2010, 1, 1);
date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd");
_
下期内容:Java8中的时间相关API实践,敬请期待!