- 问题场景复现
开发中我们经常会用到时间相关的类,想必大家对SimpleDateFormat并不陌生。但是SimpleDateFormat并不是一个线程安全的类。在多线程的情况下会出现异常(遇到坑的小伙伴深有感触吧-.-)。下面我们来分析它为什么不安全?
看下《阿里巴巴java开发手册》对于SimpleDateFormat是怎么看待的
问题场景复现
一般我们使用SimpleDateFormat的时候会把它定义为一个静态的变量,避免频繁创建它的实例对象。
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date) throws ParseException {
return sdf.format(date);
}
public static Date parse(String strDate) throws ParseException {
return sdf.parse(strDate);
}
public static void main(String[] args) throws InterruptedException, ParseException {
System.out.println(sdf.format(new Date()));
}
单线程下没什么问题,但是运用到多线程下呢?
public static void main(String[] args) throws InterruptedException, ParseException {
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i = 0; i < 20; i++) {
service.execute(() -> {
for (int j = 0; j < 10; j++) {
try {
System.out.println(parse("2018-01-02 09:45:59"));
} catch (ParseException e) {
e.printStackTrace();
}
}
});
}
// 等待上述的线程执行完
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
}
输出结果:
-
多线程不安全原因
我们把SimpleDateFormat 定义为静态变量,那么多线程下SimpleDateFormat的实例就会被多个线程共享,比如B线程可能就会读取到A线程的时间,就会出现时间差异或者直接挂了。我们看下它的源码
calendar变量是一个共享变量,可以被多个线程访问。假设A线程把时间设置成2019-04-11,A挂起,线程B执行把时间设置成2019-03-20,B挂起,A执行,这时候calendar就拿到B的值。这样就会时间不对,挂死。 -
解决方案
1.不用static修饰,什么时候用到什么时候创建实例。
public static String formatDate(Date date) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
public static Date parse(String strDate) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(strDate);
}
2.使用synchronized
public static String formatDate(Date date) throws ParseException {
synchronized(sdf){
return sdf.format(date);
}
}
public static Date parse(String strDate) throws ParseException {
synchronized(sdf){
return sdf.parse(strDate);
}
}
3.ThreadLocal
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static Date parse(String dateStr) throws ParseException {
return threadLocal.get().parse(dateStr);
}
public static String format(Date date) {
return threadLocal.get().format(date);
}
4.基于JDK1.8的DateTimeFormatter
package com.physicalexam.all.utils;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* @author b
* @date 2019/4/11
*/
public class SimpleDateFormatUtil {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static String formatDate2(LocalDateTime date) {
return formatter.format(date);
}
public static LocalDateTime parse2(String dateNow) {
return LocalDateTime.parse(dateNow, formatter);
}
public static void main(String[] args) throws InterruptedException, ParseException {
ExecutorService service = Executors.newFixedThreadPool(100);
System.out.println(LocalDateTime.now());
// 20个线程
for (int i = 0; i < 20; i++) {
service.execute(() -> {
for (int j = 0; j < 10; j++) {
try {
System.out.println(parse2(formatDate2(LocalDateTime.now())));
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
// 等待上述的线程执行完
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
}
}
你会用了么?