[译]Java SimpleDateFormat并不那么简便

原文:https://dzone.com/articles/java-simpledateformat-is-not-simple

格式化和解析日期是一项日常(且痛苦)的工作,每天都会带来一些额外的令人头疼的麻烦。

Java中一个普遍的格式化和解析日期的方法是使用SimpleDateFormat类。下面是一个常见的我们会用到类:



import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.Date;

public final class DateUtils{

    public static final SimpleDateFormatSIMPLE_DATE_FORMAT=newSimpleDateFormat("yyyy-MM-dd");

    private DateUtils() {}

    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) {
        returnSIMPLE_DATE_FORMAT.format(target);
    }
}


你认为它会按照我们期望的那样起作用吗?我们来试一下:



private static void testSimpleDateFormatInSingleThread() {

final String source="2019-01-11";

System.out.println(DateUtils.parse(source));

}

// Fri Jan 11 00:00:00 IST 2019


很好,它确实有效。再让我们用多线程来测试一下。



private static void testSimpleDateFormatWithThreads() {

ExecutorService executorService=Executors.newFixedThreadPool(10);

final String source="2019-01-11";

System.out.println(":: parsing date string ::");

IntStream.rangeClosed(0,20)

                .forEach((i)->executorService.submit(()->System.out.println(DateUtils.parse(source))));

executorService.shutdown();

}


得到的结果如下:


:: parsing date string ::

... omitted

Fri Jan 11 00:00:00 IST 2019Sat Jul 11 00:00:00 IST 2111Fri Jan 11 00:00:00 IST 2019... omitted 

这结果蛮有意思的,对吧?这是一个我们大多数在Java中格式化日期时都犯过的很常见的错误。为什么呢?

因为我们没有考虑到线程安全的问题。下面是Java doc中对于simpleDateFormat的描述:


"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."



Tip:当我们使用实例变量时,应该总是要检查它们是否是线程安全的。


如文档所说,我们可以通过给每个线程使用单独的实例来解决这个问题。但如果我们想共享一个实例呢?如何来解决?

方案1:ThreadLocal

使用ThreadLocal变量可以解决这个问题。ThreadLocal的get()方法会给我们当前线程的正确的值。



import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.Date;

public final class DateUtilsThreadLocal{

    public static final ThreadLocal SIMPLE_DATE_FORMAT=ThreadLocal.withInitial(()->newSimpleDateFormat("yyyy-MM-dd"));

    privateDateUtilsThreadLocal() {}

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

    public static String format(Date target) {
    return SIMPLE_DATE_FORMAT.get().format(target);  
    }
}


方案2:Java8 线程安全的日期-时间 API

Java8带来了一个新的日期-时间API,从而我们有了一个更好的,问题更少的SimpleDateFormate的替代品。如果我们实在是需要坚持使用SimpleDateFormat,我们可以继续使用ThreadLocal。但是当我们有了一个更好的选择时,我们应该好好考虑要不要使用它。

Java8带来了不少线程安全日期类。

Java 文档中这样说:


"This class is immutable and thread-safe."


多学习一下这些类是很有价值的,包括了DateTimeFormatter, OffsetDateTime, ZonedDateTime, LocalDateTime, LocalDate, 和 LocalTime

我们的解决办法:



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);
    }

}


结论:Java8的解决方案使用了一个不可变类,不可变类对于解决多线程问题是一个很好地实践。不可变类天生就是线程安全的,所以尽可能多的使用它们。

Happy coding!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。