SimpleDateFormat是线程非安全的
[强制]
SimpleDateFormat
是线程不安全的类(主要是该类的方法非线程安全),一般不要定义为
static
变量,如果定义为
static
, 必须加锁,或者使用 DateUtils
工具类。
正例:
注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal<DateFormat> DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
说明:
如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。
在《JAVA开发手册》中有提到上述的建议。我们就来详细解释下该建议的来龙去脉。
首先对SimpleDateFormat是非线程安全的进行一下解释。
// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
boolean useDateFormatSymbols = useDateFormatSymbols();
for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
}
switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;
case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break;
default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}
从calendar.setTime(date)这句代码可以看出,SimpleDateFormat在format方法中将入参日期对象的时间set到calendar中calendar.setTime(date),calendar是全局变量,在SimpleDateFormat的多个方法中用到,一旦出现多线程调用的情况,calendar的值就会被修改,导致结果不正确甚至发生报错,所以SimpleDateFormat是线程不安全的.
DateTimeFormatter是线程安全的
首先来看一个DateTimeFormatter格式化的实例:
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(LocalDateTime.Now());
- DateTimeFormatter#ofPattern
每次都会返回一个新的DateTimeFormatter对象
- DateTimeFormatter#format
Formats a date-time object using this formatter.
This formats the date-time to a String using the rules of the formatter.
temporal the temporal object to format, not null
the formatted string, not null
DateTimeException if an error occurs during formatting
public String format(TemporalAccessor temporal) {
StringBuilder buf = new StringBuilder(32);
formatTo(temporal, buf);
return buf.toString();
}
进入DateTimeFormatter#format函数的实现来看,其缓冲区每次调用都是新建的,进一步查看DateTimeFormatter#formatTo
- DateTimeFormatter#formatTo
Formats a date-time object to an Appendable using this formatter.
This outputs the formatted date-time to the specified destination.
Appendable is a general purpose interface that is implemented by all
key character output classes including StringBuffer, StringBuilder,
PrintStream and Writer.
Although {@code Appendable} methods throw an {@code IOException}, this method does not.
Instead, any {@code IOException} is wrapped in a runtime exception.
temporal the temporal object to format, not null
appendable the appendable to format to, not null
DateTimeException if an error occurs during formatting
public void formatTo(TemporalAccessor temporal, Appendable appendable) {
Objects.requireNonNull(temporal, "temporal");
Objects.requireNonNull(appendable, "appendable");
try {
DateTimePrintContext context = new DateTimePrintContext(temporal, this);
if (appendable instanceof StringBuilder) {
printerParser.format(context, (StringBuilder) appendable);
} else {
// buffer output to avoid writing to appendable in case of error
StringBuilder buf = new StringBuilder(32);
printerParser.format(context, buf);
appendable.append(buf);
}
} catch (IOException ex) {
throw new DateTimeException(ex.getMessage(), ex);
}
}
在DateTimeFormatter#formatTo函数中重点注意DateTimePrintContext context = new DateTimePrintContext(temporal, this);这句代码表明传入的日期被封装到一个新的DateTimePrintContext对象中,所以你可以理解DateTimeFormatter#format函数就是一个简单的函数调用,并没有使用到共享数据;所以DateTimeFormatter#format是线程安全的。