02-SimpleDateFormat为什么线程不安全
背景
阿里巴巴java开发手册中有这么一条:
【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。
那么今天我们来分析下SimpleDateFormat为什么是线程不安全的;
其实jdk8不再推荐这样使用了,可以使用LocalDate(不可以变类);以下只是感兴趣,探个究竟
错误的例子
先看下错误的例子:
/**
* @Description SimpleDateFormat 为什么线程不安全
* @Date 2020/10/10 9:26 PM
* @Created by dwb
* 微信: snail_java
*/
public class DateUtils {
private DateUtils() {}
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date) {
return DATE_FORMAT.format(date);
}
public static Date parseDateStr(String dateStr) {
try {
//为类变异代码阅读,异常在工具方法中try了
return DATE_FORMAT.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
final String dateStr = "2020-10-10 10:10:10";
//按照Java规范;线程应该放入线程池中执行;此处为了阅读方便,线程单独执行;
for (int i = 0; i < 10; i++) {
Runnable runnable = () -> DateUtils.parseDateStr(dateStr);
new Thread(runnable).start();
}
}
}
运行后
Exception in thread "Thread-5" Exception in thread "Thread-2" Exception in thread "Thread-3" Exception in thread "Thread-1" Exception in thread "Thread-0" Exception in thread "Thread-6" java.lang.NumberFormatException: empty String
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.dwb.snail.day.day02.DateUtils.parseDateStr(DateUtils.java:28)
at com.dwb.snail.day.day02.DateUtils.lambda0(DateUtils.java:40)
at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.dwb.snail.day.day02.DateUtils.parseDateStr(DateUtils.java:28)
at com.dwb.snail.day.day02.DateUtils.lambda0(DateUtils.java:40)
at java.lang.Thread.run(Thread.java:748)
看下源码结构
可以看出 SimpleDateFormat 继承 DateFormat;然而DateFormat有两个protected属性(calendar
,numberFromat
);所以SimpleDateFormat也继承了这两个属性;
因此,多线程情况下,SimpleDateFormat具有两个共享的变量,即calendar
(主要存放日期),numberFromat
;多线程情况下修改共享变量,是线程不安全的;
-
parse过程线程不安全分析
共享变量calendar
parse中有CalendarBuilder;该builder构建calendar;多线程构建,相当于多个线程同时给属性 set值,所以不安全
-
format过程线程不安全分析
共享变了calendar
实际上给calendar设置date,多线程同时set date是不安全的,再调用subFormat将date转换为字符串
如何正确使用SimpleDateFormat
-
每次调用时候,创建SimpleDateFormat(不推荐)
/** * @Description 线程安全SimpleDateFormat * @Date 2020/10/10 11:42 PM * @Created by dwb * 微信: snail_java */ public class OK1DateUtils { private OK1DateUtils() { } public static String formatDate(Date date) { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return format.format(date); } public static Date parseDateStr(String dateStr) { try { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //为类变异代码阅读,异常在工具方法中try了 return format.parse(dateStr); } catch (ParseException e) { e.printStackTrace(); } return null; } }
- 使用ThreadLocal (推荐使用)
/**
* @Description SimpleDateFormat线程安全使用方式
* @Date 2020/10/10 11:46 PM
* @Created by dwb
* 微信: snail_java
*/
public class OK2DateUtils {
private static final ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String formatDate(Date date) {
return threadLocal.get().format(date);
}
public static Date parseDateStr(String dateStr) {
try {
//为类变异代码阅读,异常在工具方法中try了
return threadLocal.get().parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
final String dateStr = "2020-10-10 10:10:10";
//按照Java规范;线程应该放入线程池中执行;此处为了阅读方便,线程单独执行;
for (int i = 0; i < 10; i++) {
Runnable runnable = () -> OK2DateUtils.parseDateStr(dateStr);
new Thread(runnable).start();
}
}
}