请用一个代码示例说明SimpleDateFormat为什么不是线程安全的。这个类有什么问题? 问题出现在SimpleDateFormat的format函数中。 请给出一个演示该类错误的代码示例。
FastDateFormat是线程安全的。为什么? SimpleDateFormat和FastDateFormat之间有什么区别?
请提供一个演示此问题的代码示例。
请用一个代码示例说明SimpleDateFormat为什么不是线程安全的。这个类有什么问题? 问题出现在SimpleDateFormat的format函数中。 请给出一个演示该类错误的代码示例。
FastDateFormat是线程安全的。为什么? SimpleDateFormat和FastDateFormat之间有什么区别?
请提供一个演示此问题的代码示例。
SimpleDateFormat
将中间结果存储在实例字段中。因此,如果一个实例被两个线程使用,它们可能会干扰彼此的结果。
查看源代码可以发现有一个Calendar
实例字段,用于操作DateFormat
/SimpleDateFormat
。
例如,parse(..)
最初调用calendar.clear()
,然后调用calendar.add(..)
。如果另一个线程在第一个调用完成之前调用parse(..)
,它将清除日历,但是其他调用将期望它填充计算的中间结果。
在不牺牲线程安全性的情况下重用日期格式的一种方法是将它们放入ThreadLocal
中 - 一些库会这样做。如果您需要在一个线程中多次使用相同的格式,则可以这样做。但是,在使用Servlet容器(具有线程池)的情况下,请记得在完成后清除线程本地变量。
说实话,我不明白为什么他们需要实例字段,但事实就是这样。您也可以使用joda-time的DateTimeFormat
,它是线程安全的。
SimpleDateFormat
是一个具体类,用于以区域敏感的方式格式化和解析日期。
根据 JavaDoc
文件,
但是日期格式不是线程同步的。建议为每个线程创建单独的实例。如果多个线程同时访问某个格式,则必须在外部进行同步。
要使 SimpleDateFormat 类线程安全,请参考 以下方法:
DateTimeFormatter
是一个不可变且线程安全的替代 SimpleDateFormat
的工具。Date
类更易于使用,并提供更多可能性。 - Ole V.V.ThreadLocal + SimpleDateFormat = SimpleDateFormatThreadSafe
package com.foocoders.text;
import java.text.AttributedCharacterIterator;
import java.text.DateFormatSymbols;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class SimpleDateFormatThreadSafe extends SimpleDateFormat {
private static final long serialVersionUID = 5448371898056188202L;
ThreadLocal<SimpleDateFormat> localSimpleDateFormat;
public SimpleDateFormatThreadSafe() {
super();
localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat();
}
};
}
public SimpleDateFormatThreadSafe(final String pattern) {
super(pattern);
localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(pattern);
}
};
}
public SimpleDateFormatThreadSafe(final String pattern, final DateFormatSymbols formatSymbols) {
super(pattern, formatSymbols);
localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(pattern, formatSymbols);
}
};
}
public SimpleDateFormatThreadSafe(final String pattern, final Locale locale) {
super(pattern, locale);
localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(pattern, locale);
}
};
}
public Object parseObject(String source) throws ParseException {
return localSimpleDateFormat.get().parseObject(source);
}
public String toString() {
return localSimpleDateFormat.get().toString();
}
public Date parse(String source) throws ParseException {
return localSimpleDateFormat.get().parse(source);
}
public Object parseObject(String source, ParsePosition pos) {
return localSimpleDateFormat.get().parseObject(source, pos);
}
public void setCalendar(Calendar newCalendar) {
localSimpleDateFormat.get().setCalendar(newCalendar);
}
public Calendar getCalendar() {
return localSimpleDateFormat.get().getCalendar();
}
public void setNumberFormat(NumberFormat newNumberFormat) {
localSimpleDateFormat.get().setNumberFormat(newNumberFormat);
}
public NumberFormat getNumberFormat() {
return localSimpleDateFormat.get().getNumberFormat();
}
public void setTimeZone(TimeZone zone) {
localSimpleDateFormat.get().setTimeZone(zone);
}
public TimeZone getTimeZone() {
return localSimpleDateFormat.get().getTimeZone();
}
public void setLenient(boolean lenient) {
localSimpleDateFormat.get().setLenient(lenient);
}
public boolean isLenient() {
return localSimpleDateFormat.get().isLenient();
}
public void set2DigitYearStart(Date startDate) {
localSimpleDateFormat.get().set2DigitYearStart(startDate);
}
public Date get2DigitYearStart() {
return localSimpleDateFormat.get().get2DigitYearStart();
}
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {
return localSimpleDateFormat.get().format(date, toAppendTo, pos);
}
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
return localSimpleDateFormat.get().formatToCharacterIterator(obj);
}
public Date parse(String text, ParsePosition pos) {
return localSimpleDateFormat.get().parse(text, pos);
}
public String toPattern() {
return localSimpleDateFormat.get().toPattern();
}
public String toLocalizedPattern() {
return localSimpleDateFormat.get().toLocalizedPattern();
}
public void applyPattern(String pattern) {
localSimpleDateFormat.get().applyPattern(pattern);
}
public void applyLocalizedPattern(String pattern) {
localSimpleDateFormat.get().applyLocalizedPattern(pattern);
}
public DateFormatSymbols getDateFormatSymbols() {
return localSimpleDateFormat.get().getDateFormatSymbols();
}
public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) {
localSimpleDateFormat.get().setDateFormatSymbols(newFormatSymbols);
}
public Object clone() {
return localSimpleDateFormat.get().clone();
}
public int hashCode() {
return localSimpleDateFormat.get().hashCode();
}
public boolean equals(Object obj) {
return localSimpleDateFormat.get().equals(obj);
}
}
SimpleDateFormat
,这可能会导致奇怪的状态!这是不一致和不线程安全的。如果SimpleDateFormat
是不可变的,那么这个解决方案就会很聪明 - https://gist.github.com/pablomoretti/9748230#gistcomment-3758032 - CodingSamples这里是一个导致奇怪错误的示例。即使在谷歌上搜索也没有任何结果:
public class ExampleClass {
private static final Pattern dateCreateP = Pattern.compile("Дата подачи:\\s*(.+)");
private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss dd.MM.yyyy");
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(100);
while (true) {
executor.submit(new Runnable() {
@Override
public void run() {
workConcurrently();
}
});
}
}
public static void workConcurrently() {
Matcher matcher = dateCreateP.matcher("Дата подачи: 19:30:55 03.05.2015");
Timestamp startAdvDate = null;
try {
if (matcher.find()) {
String dateCreate = matcher.group(1);
startAdvDate = new Timestamp(sdf.parse(dateCreate).getTime());
}
} catch (Throwable th) {
th.printStackTrace();
}
System.out.print("OK ");
}
}
并且结果:
OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK java.lang.NumberFormatException: For input string: ".201519E.2015192E2"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
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:2056)
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.nonscalper.webscraper.processor.av.ExampleClass.workConcurrently(ExampleClass.java:37)
at com.nonscalper.webscraper.processor.av.ExampleClass$1.run(ExampleClass.java:25)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
以下是一个示例,将 SimpleDateFormat 对象定义为静态字段。当两个或更多线程同时访问“someMethod”,并使用不同的日期时,它们可能会干扰彼此的结果。
public class SimpleDateFormatExample {
private static final SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
public String someMethod(Date date) {
return simpleFormat.format(date);
}
}
public class FormattedTimeHandler extends AbstractHandler {
private static final String OUTPUT_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
private static final String INPUT_TIME_FORMAT = "yyyy-MM-ddHH:mm:ss";
private static final SimpleDateFormat simpleFormat = new SimpleDateFormat(OUTPUT_TIME_FORMAT);
// apache commons lang3 FastDateFormat is threadsafe
private static final FastDateFormat fastFormat = FastDateFormat.getInstance(OUTPUT_TIME_FORMAT);
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);
final String inputTime = request.getParameter("time");
Date date = LocalDateTime.parse(inputTime, DateTimeFormat.forPattern(INPUT_TIME_FORMAT)).toDate();
final String method = request.getParameter("method");
if ("SimpleDateFormat".equalsIgnoreCase(method)) {
// use SimpleDateFormat as a static constant field, not thread safe
response.getWriter().println(simpleFormat.format(date));
} else if ("FastDateFormat".equalsIgnoreCase(method)) {
// use apache commons lang3 FastDateFormat, thread safe
response.getWriter().println(fastFormat.format(date));
} else {
// create new SimpleDateFormat instance when formatting date, thread safe
response.getWriter().println(new SimpleDateFormat(OUTPUT_TIME_FORMAT).format(date));
}
}
public static void main(String[] args) throws Exception {
// embedded jetty configuration, running on port 8090. change it as needed.
Server server = new Server(8090);
server.setHandler(new FormattedTimeHandler());
server.start();
server.join();
}
代码和jmeter脚本可以在这里下载。
NumberFormatException
/ ArrayIndexOutOfBoundsException
异常,并且它们会“悄悄地”终止线程。此外,线程没有被加入,这是不好的。请查看 LANG-909
中的类 - 我认为它们看起来更好。 - dma_kstatic private SimpleDateFormat sdf = new SimpleDateFormat("....");
synchronized(sdf)
{
// use the instance here to format a date
}
// The above makes it thread safe
format
和parse
方法就是线程安全的。目前我正在搜索并修复我们代码库中所有这些SimpleDateFormat的用法 :/ - Erich KitzmuellerDateFormat
、SimpleDateFormat
、Date
和Calendar
类。这些可怕的类现在都已经过时了。它们早在多年前就被JSR 310中定义的现代java.time类所取代。java.time类是通过设计线程安全,使用不可变对象来实现的。 - Basil Bourque