SimpleDateFormat两个著名的坑

坑1:定义的static的SimpleDateFormat 可能出现线程安全问题

SimpleDateFormat是线程不安全的类,定义为static对象,会有数据同步风险。通过源码可以看出,SimpleDateFormat内部有一个Calendar对象,在日期转字符串或字符串转日期的过程中,多线程共享时有非常高的概率产生错误,推荐的方式之一时使用ThreadLocal,让每个线程单独拥有这个对象。

示例代码:

public class SimpleDateFormatterTest {
    
    
    static ExecutorService threadPool = Executors.newFixedThreadPool(20);

    static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) {
    
    
        for(int i=0;i<200;i++){
    
    
            threadPool.execute(()->{
    
    
                try {
    
    
                    System.out.println(format.parse("2021-07-09 16:29:21"));
                } catch (ParseException e) {
    
    
                    e.printStackTrace();
                };
            });
        }
    }
}

运行程序后大量报错,且没有报错的输出结果也不正确

在这里插入图片描述
在这里插入图片描述
为什么会出现上述问题呢?
SimpleDateFormat 的作用是的另一解析和格式化日期时间的模式,这看起来是一次性工作。应该复用,但它的解析和格式化的操作是非线程安全的。

*SimpleDateFormat 继承 DateFormat ,DateFormat 有一个成员变量 calendar。

SimpleDateFormat 的parse 方法如下:

    public Date parse(String source) throws ParseException
    {
    
    
        ParsePosition pos = new ParsePosition(0);
        Date result = parse(source, pos);
        if (pos.index == 0)
            throw new ParseException("Unparseable date: \"" + source + "\"" ,
                pos.errorIndex);
        return result;
    }
 

最终会调用CalendarBuilder的establish 方法来构建Calendar

parsedDate = calb.establish(calendar).getTime();

establish 方法内部是先清空 Calendar再构建Calendar,整个的操作没有加锁

 Calendar establish(Calendar cal) {
    
    
    ......

        cal.clear();
   
    ......
        return cal;
    }
    ```
 如果多线程在并发操作一个Calendar, 可能会产生一个线程还没来得及处理Calendar 就被另外一个线程清空了,所以会出现解析错误和异常。

那么怎么解决呢?

* 每次使用时new一个SimpleDateFormat 的 实例,这样可以保证每个实例使用自己的Calendar实例,但是每次使用都需要new一个对象,并且使用后由于没有其他引用,又需要回收,开销会很大。
* 可以使用syncheronized 对SimpleDtaFormat实例进行同步
* 【推荐】 使用ThreadLocl,这样每个线程只需要使用一个SimpleDateFormate实例,这相比第一种方式 节省了对象的创建销毁开销,并且不需要使多个线程同步。
* 使用Java8的DateTimeFormatter 类

下面是用ThreadLocal实现的示例:
```java
public class SimpleDateFormatterTest {
    
    
    static ExecutorService threadPool = Executors.newFixedThreadPool(20);
    private static  final ThreadLocal<SimpleDateFormat> SIMPLEDATEFORMAT_THREADLOCAL = new ThreadLocal<SimpleDateFormat>(){
    
    
        @Override
        protected SimpleDateFormat initialValue() {
    
    
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) throws InterruptedException {
    
    
        for(int i=0;i<200;i++){
    
    
            threadPool.execute(()->{
    
    
                try {
    
    
                    System.out.println(SIMPLEDATEFORMAT_THREADLOCAL.get().parse("2021-07-09 16:29:21"));
                } catch (ParseException e) {
    
    
                    e.printStackTrace();
                };
            });
        }
        threadPool.shutdown();
        threadPool.awaitTermination(10, TimeUnit.SECONDS);
    }
}

坑2:当需要解析的字符串和格式不匹配的时候,SimpleDateFormat 并不报错,而是返回其他日期

    public static void main(String[] args) throws InterruptedException, ParseException {
    
    
       String dateString = "20210908";
       SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMM");
       System.out.println(simpleDateFormat.parse(dateString));
    }

结果:
Wed Aug 01 00:00:00 CST 2096

竟然输出了 2096年了

对于上面的两个坑,我们可以使用java8的DateTimeFormatter 来避免

1) 解决第一个坑

public class DateTimeFormatterTest {
    
    
    static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    static ExecutorService threadPool = Executors.newFixedThreadPool(20);

    public static void main(String[] args) throws InterruptedException {
    
    
        for(int i=0;i<200;i++){
    
    
            threadPool.execute(()->{
    
    
                  System.out.println(dateTimeFormatter.parse("2021-07-09 16:29:21"));
            });
        }
        threadPool.shutdown();
        threadPool.awaitTermination(10, TimeUnit.SECONDS);
    }

}
  1. 解决第二个坑
 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM");
        System.out.println(dateTimeFormatter.parse("2021-07-09 16:29:21"));

此时会直接报错,而不会出现不正确的结果;

DateTimeFormatter 是线程安全的,可以定义为static 使用,最后,DateTimeFormatter 的解析比较严格,需要解析的字符串和格式不匹配时,会直接报错。而不是错误的解析。

猜你喜欢

转载自blog.csdn.net/fd2025/article/details/118609555#comments_21618216