前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SimpleDateFormat线程不安全示例及其解决方法

SimpleDateFormat线程不安全示例及其解决方法

作者头像
孟君
发布2019-10-10 10:48:45
9340
发布2019-10-10 10:48:45
举报

我们可以用java.text.SimpleDateFormat类完成日期的转换和格式化操作,如:

代码语言:javascript
复制
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

  private static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(
      "yyyyMMdd");

  public Date parseDate(String value) throws ParseException {
    return SIMPLE_DATE_FORMAT.parse(value);
  }

}
代码语言:javascript
复制
import java.text.ParseException;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class Main {

  public static void main(String[] args) throws ParseException {
    SimpleDateFormatExample example = new SimpleDateFormatExample();
    Date date = example.parseDate("20161118");
    //Fri Nov 18 00:00:00 CST 2016
    System.out.println(date);
  }
}

但是,同时,我们也能从java.text.SimpleDateFormat类的javadoc中看到如下一句话。

代码语言:javascript
复制
 Date formats are not synchronized.
 It is recommended to create separate format instances for each thread.
 If multiple threads access a format concurrently, it must be synchronized externally.

Date formats没有同步。 建议为每一个线程创建独立的format对象。 如果多个线程并发访问一个format,那么,一定要在外部实现同步(synchronized)。

也就是说,

在多线程下我们需要做些额外的保护措施,去保证其正确处理,否则是不安全的。

让我们一起来看一下,多线程下会出现什么问题。

一、线程不安全示例

代码语言:javascript
复制
package my.format;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

  private static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(
      "yyyyMMdd");

  public Date parseDate(String value) throws ParseException {
    return SIMPLE_DATE_FORMAT.parse(value);
  }

}

多线程测试示例:

代码语言:javascript
复制
package my.format;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Main {

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

    int availableProcessors = Runtime.getRuntime().availableProcessors();
    ExecutorService exec = Executors
        .newFixedThreadPool(availableProcessors);

    List<Future<Date>> results = new ArrayList<>();

    final SimpleDateFormatExample sdf = new SimpleDateFormatExample();
    Callable<Date> parseDateTask = new Callable<Date>() {
      public Date call() throws Exception {
        return sdf.parseDate("20161118");
      }
    };

    for (int i = 0; i < 10; i++) {
      results.add(exec.submit(parseDateTask));
    }
    
    exec.shutdown();

    /**
     * 查看结果
     */
    for (Future<Date> result : results) {
      System.out.println(result.get());
    }
  }
}

运行结果主要包含如下几个错误:

  • 无异常,日期解析出现错误
代码语言:javascript
复制
Tue Nov 18 00:00:00 CST 2200
Tue Nov 18 00:00:00 CST 2200
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Thu Nov 18 00:00:00 CST 1
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
  • 有异常,java.lang.NumberFormatException

如:

代码语言:javascript
复制
Fri Nov 18 00:00:00 CST 2016
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: ""
  at java.util.concurrent.FutureTask.report(Unknown Source)
  at java.util.concurrent.FutureTask.get(Unknown Source)
  at my.format.Main.main(Main.java:40)
Caused by: java.lang.NumberFormatException: For input string: ""
  at java.lang.NumberFormatException.forInputString(Unknown Source)
  at java.lang.Long.parseLong(Unknown Source)
  at java.lang.Long.parseLong(Unknown Source)
  at java.text.DigitList.getLong(Unknown Source)
  at java.text.DecimalFormat.parse(Unknown Source)
  at java.text.SimpleDateFormat.subParse(Unknown Source)
  at java.text.SimpleDateFormat.parse(Unknown Source)
  at java.text.DateFormat.parse(Unknown Source)
  at my.format.SimpleDateFormatExample.parseDate(SimpleDateFormatExample.java:14)
  at my.format.Main$1.call(Main.java:27)
  at my.format.Main$1.call(Main.java:1)
  at java.util.concurrent.FutureTask.run(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
  at java.lang.Thread.run(Unknown Source)

再如:

代码语言:javascript
复制
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: "1111.E1111E22"
  at java.util.concurrent.FutureTask.report(Unknown Source)
  at java.util.concurrent.FutureTask.get(Unknown Source)
  at my.format.Main.main(Main.java:40)
Caused by: java.lang.NumberFormatException: For input string: "1111.E1111E22"
  at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
  at java.lang.Double.parseDouble(Unknown Source)
  at java.text.DigitList.getDouble(Unknown Source)
  at java.text.DecimalFormat.parse(Unknown Source)
  at java.text.SimpleDateFormat.subParse(Unknown Source)
  at java.text.SimpleDateFormat.parse(Unknown Source)
  at java.text.DateFormat.parse(Unknown Source)
  at my.format.SimpleDateFormatExample.parseDate(SimpleDateFormatExample.java:14)
  at my.format.Main$1.call(Main.java:27)
  at my.format.Main$1.call(Main.java:1)
  at java.util.concurrent.FutureTask.run(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
  at java.lang.Thread.run(Unknown Source)

那么问题来了,如何保证运行正常呢?

二、解决方法

其实,从SimpleDateFormat的javadoc中已经看到有处理的方法了。

代码语言:javascript
复制
Date formats are not synchronized.
It is recommended to create separate format instances for each thread.
If multiple threads access a format concurrently, it must be synchronized externally.

接下来,先从这个描述信息给出相关的解决方法。

2.1 每次都新建SimpleDateFormat对象

改造SimpleDateFormatExample类,如:

代码语言:javascript
复制
package my.format;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

  public Date parseDate(String value) throws ParseException {
    return new SimpleDateFormat(
        "yyyyMMdd").parse(value);
  }

}

执行上述Main.java类,得到正确结果:

代码语言:javascript
复制
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016

2.2 访问format时,添加synchronized

改造SimpleDateFormatExample类,如:

代码语言:javascript
复制
package my.format;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

  private static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(
      "yyyyMMdd");

  public Date parseDate(String value) throws ParseException {
    synchronized (SIMPLE_DATE_FORMAT) {
      return SIMPLE_DATE_FORMAT.parse(value);
    }
  }

}

或者在使用format对象的方法前添加synchronized修饰,如:

代码语言:javascript
复制
package my.format;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

  private static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(
      "yyyyMMdd");

  public synchronized Date parseDate(String value) throws ParseException {
    return SIMPLE_DATE_FORMAT.parse(value);
  }

}

同样,执行上述Main.java类,可以得到正确结果:

代码语言:javascript
复制
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016

2.3 使用TheadLocal

改造SimpleDateFormatExample类,如:

代码语言:javascript
复制
package my.format;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

  private static final ThreadLocal<DateFormat> THREAD_LOCAL_DATE_FORMAT = new ThreadLocal<DateFormat>() {
    protected DateFormat initialValue() {
      return new SimpleDateFormat("yyyyMMdd");
    }
  };

  public Date parseDate(String value) throws ParseException {
    return THREAD_LOCAL_DATE_FORMAT.get().parse(value);
  }

}

同样,执行上述Main.java类,可以得到正确结果:

代码语言:javascript
复制
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016

2.4 使用FastDateFormat

FastDateFormat类在Apache Common Langs包下面, 该类是线程安全的。

如果是Maven工程,其添加依赖包如下:

代码语言:javascript
复制
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.5</version>
    </dependency>

改造SimpleDateFormatExample类,如:

代码语言:javascript
复制
  private static final FastDateFormat SIMPLE_DATE_FORMAT = FastDateFormat
      .getInstance("yyyyMMdd");

完成的类为:

代码语言:javascript
复制
package my.format;

import java.text.ParseException;
import java.util.Date;

import org.apache.commons.lang3.time.FastDateFormat;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

  private static final FastDateFormat SIMPLE_DATE_FORMAT = FastDateFormat
      .getInstance("yyyyMMdd");

  public Date parseDate(String value) throws ParseException {
    return SIMPLE_DATE_FORMAT.parse(value);
  }

}

同样,执行上述Main.java类,可以得到正确结果:

代码语言:javascript
复制
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016

2.5 使用Joda Time

DateTimeFormatter 类Joda-Time包下面, 该类是线程安全的。

如果是Maven工程,其添加依赖包如下:

代码语言:javascript
复制
    <!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
    <dependency>
      <groupId>joda-time</groupId>
      <artifactId>joda-time</artifactId>
      <version>2.9.6</version>
    </dependency>

改造SimpleDateFormatExample类,如:

代码语言:javascript
复制
package my.format;

import java.text.ParseException;
import java.util.Date;

import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

  private static final DateTimeFormatter SIMPLE_DATE_FORMAT = DateTimeFormat
      .forPattern("yyyyMMdd");

  public Date parseDate(String value) throws ParseException {
    return SIMPLE_DATE_FORMAT.parseDateTime(value).toDate();
  }

}

同样,执行上述Main.java类,可以得到正确结果:

代码语言:javascript
复制
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016

三、小结

本文首先给出了一个使用SimpleDateFormat的简单示例;接着,给出了一个在多线程下使用SimpleDateFormat出现问题的例子;紧接着又给出了几个解决SimpleDateFormat线程不安全使用的例子。

现在,Java 8中,已经对时间进行着改造,也已经逐渐向不可变和线程安全方向靠拢,有兴趣的读者可以尝试一下。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟君的编程札记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、线程不安全示例
  • 二、解决方法
    • 2.1 每次都新建SimpleDateFormat对象
      • 2.2 访问format时,添加synchronized
        • 2.3 使用TheadLocal
          • 2.4 使用FastDateFormat
            • 2.5 使用Joda Time
            • 三、小结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档