简述: SimpleDateFormat线程不安全,DateTimeFormatter线程安全.
SimpleDateFormat
代码分析:
/**
* The {@link Calendar} instance used for calculating the date-time fields
* and the instant of time. This field is used for both formatting and
* parsing.
*
* <p>Subclasses should initialize this field to a {@link Calendar}
* appropriate for the {@link Locale} associated with this
* <code>DateFormat</code>.
* @serial
*/
protected Calendar calendar;
// 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;
}
SimpleDateFormat在format方法中将入参日期对象的时间set到calendar中calendar.setTime(date)
,calendar是全局变量,在SimpleDateFormat的多个方法中用到,一旦出现多线程调用的情况,calendar的值就会被修改,导致结果不正确甚至发生报错,所以是线程不安全的.
例如:
import org.junit.Test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DateTimeFormatterTest {
public SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
@Test
public void testUnsafeSimpleDateFormat() {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while (true) {
threadPool.execute(() -> {
System.out.println("日期范围: "+getDaysBetween("2020-11-22"));
});
}
}
public List<String> getDaysBetween(String start) {
List<String> dates = new ArrayList<>();
try {
Date date = sdf.parse(start);
Calendar instance = Calendar.getInstance();
Calendar now = Calendar.getInstance();
instance.setTime(date);
while (instance.before(now)) {
dates.add(sdf.format(instance.getTime()));
instance.add(Calendar.DAY_OF_MONTH, 1);
}
} catch (ParseException e) {
//
}
return dates;
}
}
DateTimeFormatter
以ISO_LOCAL_DATE
(即’1970-01-01’格式)为例
实例化过程:
public static final DateTimeFormatter ISO_LOCAL_DATE;
static {
ISO_LOCAL_DATE = new DateTimeFormatterBuilder()
.appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendLiteral('-')
.appendValue(MONTH_OF_YEAR, 2)
.appendLiteral('-')
.appendValue(DAY_OF_MONTH, 2)
.toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
}
- appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD): 新建一个NumberPrinterParser对象(解析/输出YEAR)并保存到
printerParsers
集合中 - appendLiteral(’-’): 新建一个CharLiteralPrinterParser对象并保存到
printerParsers
集合中 - appendValue(MONTH_OF_YEAR, 2): 新建一个NumberPrinterParser对象(解析/输出MONTH_OF_YEAR)并保存到
printerParsers
集合中 - appendLiteral(’-’): 新建一个CharLiteralPrinterParser对象并保存到
printerParsers
集合中 - appendValue(DAY_OF_MONTH, 2): 新建一个NumberPrinterParser对象(解析/输出DAY_OF_MONTH)并保存到
printerParsers
集合中 - toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE): 新建一个DateTimeFormatter对象,如下:
private DateTimeFormatter toFormatter(Locale locale, ResolverStyle resolverStyle, Chronology chrono) {
Objects.requireNonNull(locale, "locale");
while (active.parent != null) {
optionalEnd();
}
CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false);
return new DateTimeFormatter(pp, locale, DecimalStyle.STANDARD,
resolverStyle, null, chrono, null);
}
对LocalDate做格式化处理时,LocalDate#format
:
@Override // override for Javadoc and performance
public String format(DateTimeFormatter formatter) {
Objects.requireNonNull(formatter, "formatter");
return formatter.format(this);
}
查看DateTimeFormatter#format
:
public String format(TemporalAccessor temporal) {
StringBuilder buf = new StringBuilder(32);
formatTo(temporal, buf);
return buf.toString();
}
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);
}
}
从代码可见, LocalDate对象被封装在一个新建的DateTimePrintContext
对象中,作为入参传给printerParser对象的format方法,此时不会有线程安全问题.
printerParser就是前面toFormatter
方法中的new CompositePrinterParser
对象, 再点进printerParser.format
方法,
@Override
public boolean format(DateTimePrintContext context, StringBuilder buf) {
int length = buf.length();
if (optional) {
context.startOptional();
}
try {
for (DateTimePrinterParser pp : printerParsers) {
if (pp.format(context, buf) == false) {
buf.setLength(length); // reset buffer
return true;
}
}
} finally {
if (optional) {
context.endOptional();
}
}
return true;
}
可以看到,里面是最初保存在printerParsers
集合中的那5个DateTimePrinterParser对象在循环调用各自的format方法做格式化处理,并将结果拼接在StringBuilder对象中.
以上分析可知,DateTimeFormatter在格式化日期时, 日期对象和StringBuilder对象自始至终都是作为局部变量传入到方法中,而不是像SimpleDateFormat那样把日期对象设置到全局变量calendar中进行处理,从而避免了线程安全的问题.