为什么Java的SimpleDateFormat不是线程安全的?

Viv*_*rma 226 java thread-safety simpledateformat

请告诉代码示例为什么SimpleDateFormat不是线程安全的.这堂课有什么问题? SimpleDateFormat的格式功能有问题吗?请给出一个在课堂上演示此错误的代码.

FastDateFormat是线程安全的.为什么?b/w SimpleDateFormat和FastDateFormat有什么区别?

请用解释此问题的代码解释一下?

Boz*_*zho 246

SimpleDateFormat将中间结果存储在实例字段中.因此,如果两个线程使用一个实例,它们可能会混淆对方的结果.

查看源代码显示有一个Calendar实例字段,由DateFormat/ 上的操作使用SimpleDateFormat

例如,最初和然后parse(..)调用.如果另一个线程在第一次调用完成之前调用,它将清除日历,但是另一个调用将期望它用计算的中间结果填充.calendar.clear()calendar.add(..)parse(..)

在没有交易线程安全的情况下重用日期格式的一种方法是将它们放入ThreadLocal- 一些库中这样做.如果你需要在一个线程中多次使用相同的格式.但是如果您正在使用servlet容器(具有线程池),请记住在完成后清理本地线程.

说实话,我不明白他们为什么需要实例字段,但就是这样.你也可以使用线程安全的joda-time DateTimeFormat.

  • 他们不需要*实例字段; 毫无疑问,这是由于在效率的错误尝试中草率编程的结果.真正令人难以置信的事情是很久以前这个陷阱门并没有被关闭.我认为真正的答案是避免使用java.util.Date和Calendar. (64认同)
  • 这在JDK8本身尚未修复.但JDK8引入了新的java.time包,包括线程安全的DateTimeFormatter. (25认同)
  • 这是在JDK8中修复的吗?如果没有,那为什么不呢? (4认同)
  • 在不破坏向后兼容性的情况下,它无法"固定".最好不要管它,让新代码只使用更新的,线程安全的替代品.. (3认同)
  • @whirlwin如果你不改变界面...... (2认同)
  • @Enerccio ......那么理论上,是的,这不会破坏任何东西.在实践中,我保证在几十年内(是的,几十年来,SimpleDateFormat自1.1以来一直存在,这是在1996年发布的,超过20年前)以来,很多人都依赖缺乏线程安全性的东西或其他.这是一种可怕的做法吗?是的,很明显.这是否意味着它不会发生?我希望. (2认同)
  • @Nic 我对人性的信心消失了:/ (2认同)

sgo*_*les 59

SimpleDateFormat 是一个具体的类,用于以区域设置敏感的方式格式化和解析日期.

来自JavaDoc,

但日期格式不同步.建议为每个线程创建单独的格式实例.如果多个线程同时访问格式,it must be synchronized externally.

要使SimpleDateFormat类是线程安全的,请查看以下方法:

  • 这看起来像一个很好的总结,但我不同意作者的第二点.不知何故,我怀疑同步日期格式将成为您服务器上的阻塞点.根据Knuth的说法,这是需要过早优化的3%的案例之一,还是属于97%,"我们应该忘记小的低效率"?现在,我已经看到了人们,使用自定义Web框架,在同步块中包装控制器,因此除了包括数据库调用,业务逻辑之外的所有访问 - 然后在性能测试上花费了大量精力.难怪在那里,他们在3%. (9认同)
  • @michaelok我必须同意!我认为它只是反过来 - 使用一个Single Dateformatter而不是在需要时创建一个新的格式是过早优化.您应该首先执行Easy操作:只需在需要时使用新实例. - 只有当这成为一个性能问题(内存,GBC)时,你应该考虑一个共享实例 - 但请记住:你在Threads之间共享的任何东西都可以成为一个无声的竞争条件,等待你的爆发. (9认同)
  • 顺便说一下.一个简单的点可能是一个线程卡在Dateformatter的例程中,因为无论出现什么问题 - 当他们试图访问DateFormatter时,你的网络服务器上的每个和每个线程都会被卡住...... DED ;-) (4认同)
  • 问题是什么 - 由于同步日期格式,您遇到了瓶颈?或者线程安全的问题?James Turner 上面提到的一件事是 Java 8 的 Formatter 是线程安全的:https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html (2认同)

The*_*ect 38

DateTimeFormatter在Java 8中是不可变和线程安全的替代SimpleDateFormat.

  • @SaadBenbouzid 认为这是一个优势。现代类比过时的`Date` 类更好用,并提供更多的可能性。 (2认同)

Pab*_*tti 32

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);
    }

}
Run Code Online (Sandbox Code Playgroud)

https://gist.github.com/pablomoretti/9748230

  • 我非常怀疑线程查找和同步的开销是否大于每次创建新实例的成本 (6认同)

dma*_*a_k 15

版本3.2 commons-lang将具有FastDateParser类,它是SimpleDateFormat格里高利历的线程安全替代品.有关LANG-909更多信息,请参阅


use*_*807 10

这是导致奇怪错误的示例.即便谷歌也没有结果:

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 ");
}
}
Run Code Online (Sandbox Code Playgroud)

结果:

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)
Run Code Online (Sandbox Code Playgroud)


ylu*_*ylu 5

这是一个将 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);
         }
    }
Run Code Online (Sandbox Code Playgroud)

你可以创建一个像下面这样的服务,并使用 jmeter 来模拟并发用户使用相同的 SimpleDateFormat 对象格式化不同的日期,他们的结果会被弄乱。

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();
}
Run Code Online (Sandbox Code Playgroud)

}

代码和 jmeter 脚本可以在这里下载。