任何人都可以给我一个显示SimpleDateFormat线程不安全的例子吗?

时间:2016-12-15 06:45:55

标签: java multithreading thread-safety simpledateformat

我试图写一个例子来说明SimpleDateFormat是线程不安全的。但它不起作用!任何人都可以给我一个显示SimpleDateFormat线程不安全的例子吗?

public static void main(String[] args) throws ParseException, InterruptedException {

    Date aDate = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-12-15 23:59:59"));

    ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 1000);

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    DataFormatter callable = new DataFormatter(sdf, aDate);
    Collection<DataFormatter> callables = Collections.nCopies(1000, callable);
    executor.invokeAll(callables);
    executor.shutdown();
}

private static class DataFormatter implements Callable<String> {

    private SimpleDateFormat sdf;
    private Date aDate;

    public DataFormatter(SimpleDateFormat sdf, Date aDate) {
        this.sdf = sdf;
        this.aDate = aDate;
    }

    @Override
    public String call() throws Exception {
        String format = sdf.format(aDate);
        Assert.assertEquals("2016-12-15 23:59:59", format);
        return format;
    }
}

2 个答案:

答案 0 :(得分:2)

  

有人能给我一个显示SimpleDateFormat线程不安全的例子吗?

不确定。您的代码的问题在于您尝试一遍又一遍地格式化相同日期,因此共享字段永远不会保存不同的值。如果我们查看SimpleDateFormat中的代码,我们会看到它扩展了DateFormat,其中包含共享的Calendar calendar字段。那是班级的再入问题。

// shared with everyone else calling the same SimpleDateFormat
protected Calendar calendar;
...
// method from DateFormat that is extended by SimpleDateFormat
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
    calendar.setTime(date);
    ...

另外,请注意它使用StringBuffer,这意味着它使用synchronized方法。令人沮丧的是,我们为同步性能处罚付出了代价,但我们没有使用SimpleDateFormat进行重新插入。

这是我对如何展示它的看法。我只是在随机日期运行两次日期格式并检查结果。它只有20个并发线程立即失败。

public class SimpleDateFormatEnosafe {

    private static final SimpleDateFormat format =
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        DataFormatter formatter = new DataFormatter();
        for (int i = 0; i < 20; i++) {
            executor.submit(formatter);
        }
        executor.shutdown();
        // NOTE: this could never finish if all but one thread fails in the pool
    }

    private static class DataFormatter implements Runnable {
        @Override
        public void run() {
            ThreadLocalRandom random = ThreadLocalRandom.current();
            while (true) {
                Date date = new Date(random.nextLong());
                String output1 = format.format(date);
                String output2 = format.format(date);
                if (!output1.equals(output2)) {
                    System.out.println(output1 + " != " + output2);
                    break;
                }
            }
        }
    }
}

答案 1 :(得分:0)

我试过了,结果很简单。我有两个线程使用共享SimpleDateFormat实例格式化不同的日期(声明DateFormat,但实际的实例是类SimpleDateFormat)。这是我的代码:

public class FormattingThread extends Thread {

    private final static DateFormat shared = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, Locale.GERMANY);
    private final static ZoneOffset offset = ZoneOffset.ofHours(5);

    LocalDateTime date;

    public FormattingThread(LocalDateTime date) {
        super("FormattingThread");
        this.date = date;
    }

    @Override
    public void run() {
        final DateFormat myOwn = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, Locale.GERMANY);
        int stepCount = 0;
        while (true) {
            stepCount++;
            Date classical = Date.from(date.toInstant(offset));
            String formatted = myOwn.format(classical);
            String formattedThreadUnsafe = shared.format(classical);
            if (! formatted.equals(formattedThreadUnsafe)) {
                System.err.println("Mine " + formatted + "; shared " + formattedThreadUnsafe
                        + " in " + stepCount + " steps");
            }
            date = date.plusDays(23);
        }
    }

    public static void main(String[] args) {
        new FormattingThread(LocalDateTime.now()).start();
        new FormattingThread(LocalDateTime.now().plusHours(17)).start();
    }

}

在我的计算机上,其中一个线程在第一次调用format()时得到的结果不正确。在每个帖子发出几百个电话后,ArrayIndexOutOfBoundsException课程中出现Calendar SimpleDateFormat使用Calendar

我仍然想要强调已经说过:一个类不是线程安全的,并不保证你可以在多个线程中使用它时发生错误。同步错误的故事在彻底的测试中没有显示出来,然后在生产中浮出水面,这些故事非常多。我最近在Java程序中有一个在Mac和Linux上没有发生过,但Windows用户报告了它,我们必须快速发布错误修复版本。这类事件非常昂贵。