java 简单、安全、高效的格式化 Date

349 阅读1分钟

SimpleDateFormat线程不安全

例子


import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class SimpleDateFormatNotThreadSafe {
    private static final SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Test
    public void testSimpleDateFormatNotThreadSafe() {
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        while (true) {
            executorService.execute(() -> {
                try {
                    System.out.println(simpleFormat.parse("2022-03-13 15:17:27"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

以上代码创建了SimpleDateFormat的一个实例,创建100个线程,每个线程都公用同一个sdf对象对文本日期进行解析,多运行几次就会抛出java.lang.NumberFormatException异常,加大线程的个数有利于该问题复现。

如何保证线程安全地格式化Date

利用ThreadLocal保证SimpleDateFormat线程安全

  1. 每次 new (实例化) SimpleDateFormat
  2. 利用 ThreadLocal 确保每个线程都可以得到单独的一个 SimpleDateFormat
public class DateUtil {
	private static final ThreadLocal<SimpleDateFormat> local = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

	public static String format(Date date) {
		return local.get().format(date);
	}

	public static Date parse(String dateStr) throws ParseException {
		return local.get().parse(dateStr);
	}
}

采用FastDateFormat类

依赖:

<dependency> <groupId>org.apache.commons</groupId> 
    <artifactId>commons-lang3</artifactId> 
    <version>${commons-lang3-version}</version> 
</dependency>
public class DateUtil {
    private static final FastDateFormat fastDateFormat = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");
    private static final FastDateFormat fastDateFormatWithTimeZone = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone(ZoneId.systemDefault()));


    public static String format(Date date) {
        return fastDateFormat.format(date);
    }

    public static Date parse(String dateStr) throws ParseException {
        return fastDateFormat.parse(dateStr);
    }

    public static void main(String[] args) throws ParseException {
        Date date = new Date();
        System.out.println(format(date));
        String dateStr = "2022-03-14 14:24:26";
        System.out.println(parse(dateStr));
        System.out.println(ZoneId.systemDefault());
    }
}

利用 Instant + DateTimeFormatter


public class DateUtil {
    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
    public static String format(Date date) {
        return DATETIME_FORMATTER.format(date.toInstant());
    }
}