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线程安全
- 每次
new(实例化)SimpleDateFormat。 - 利用
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());
}
}