Java 中的 SimpleDateFormat 并不 Simple

785 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

在 Java 中日期和字符串之间的转化是非常频繁的一个动作,自然的我们就会很容易的想到使用 SimpleDateFormat 这个类,今天就是说一下这个类有什么问题,我们又该如何处理日期和字符串的转化。

下面先看一段看起来正确实际上有问题的代码。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public final class DateUtils {
    public static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

    public static Date parse(String target) {
        try {
            return SIMPLE_DATE_FORMAT.parse(target);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String format(Date target) {
        return SIMPLE_DATE_FORMAT.format(target);
    }
}

这段代码在单线程环境中是没有问题的,但是问题就是 SimpleDateFormat 这个类并不是线程安全的,故在多线程环境中会报错。

我们该怎么办,只需要保证每个线程使用的 SimpleDateFormat 对象不一样即可,先说一种比较简单的做法,既是将对象定义的语句放在方法体内。这样即便是多线程,每次执行方法都会有创建一个新的 format 对象,方法结束对象消失,这肯定就不会存在安全问题。

当然,上面那种方法是不推荐的,一个大型项目中转换方法可能会被调用成百上千次,这样就会反复创建回收对象,实在是影响性能。

在 JDK1.8 之前,我们可以使用 ThreadLocal 来保存 SimpleDateFormat 对象,呃呃,可以把 ThreadLocal 理解为一个线程私有的 Map,这个 Map 中存储的对象只有本线程独享。这样每个线程都拥有自己的 format 对象,就不会出现安全问题。

但是,现在已经有更好的方法了,JDK1.8 中提供了一些线程安全的转换类。 DateTimeFormatter, OffsetDateTime, ZonedDateTime, LocalDateTime, LocalDate, and LocalTime 。这里看一个 DateTimeFormatter 的使用即可。

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class DateUtilsJava8 {
    public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private DateUtilsJava8() {}
    public static LocalDate parse(String target) {
        return LocalDate.parse(target, DATE_TIME_FORMATTER);
    }
    public static String format(LocalDate target) {
        return target.format(DATE_TIME_FORMATTER);
    }
}

多说一句,为什么 DateTimeFormatter 是线程安全的呢,因为它是一个不可变类,不可变类天生就是线程安全的。

多看一眼文档中对 DateTimeFormatter 的描述。

 * This class is immutable and thread-safe.
 *
 * @since 1.8
 */
public final class DateTimeFormatter {

所以,请忘记 SimpleDateFormat 吧,使用 DateTimeFormatter 就好。实在不行,就使用 ThreadLocal 包装一下。