SimpleDateFormat是线程安全的吗

我们在对日期进行格式化时,除了用第三方的工具类,SimpleDateFormat应该是用的最多的。但大部分都是在单线程的环境下,那么SimpleDateFormat是线程安全的吗?我们首先写一个测试用例。

public class TestSimpleDateFormat {
    public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    public static void main(String[] args) {
        for (int i=0; i<10; i++) {
            Thread thread = new Thread(() -> {
                try {
                    System.out.println(sdf.parse("2021-12-29 21:32:33"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
        }
    }
}

第一次执行并没有报错,第二次执行时结果如下:

java.lang.NumberFormatException: empty String
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)

可见SimpleDateFormat不是线程安全的,在解释报错原因之前,我们先看一下SimpleDateFormat的类继承结构图:
在这里插入图片描述

SimpleDateFormat继承了DateFormat,DateFormat内部有一个Calendar对象的引用,主要用来存储和SimpleDateFormat相关的日期信息

SimpleDateFormat对parse()方法的实现。关键代码如下:

@Override
public Date parse(String text, ParsePosition pos) {
    ...省略中间代码
    Date parsedDate;
    try {
        ...
        parsedDate = calb.establish(calendar).getTime();
    } catch (IllegalArgumentException e) {
       ...
    }

    return parsedDate;
}

establish()的实现如下:

Calendar establish(Calendar cal) {
    ...
    cal.clear();
    for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
        for (int index = 0; index <= maxFieldIndex; index++) {
            if (field[index] == stamp) {
                cal.set(index, field[MAX_FIELD + index]);
                break;
            }
        }
    }
    ...
    return cal;
}

在多个线程共享SimpleDateFormat时,同时也共享了Calendar引用,在如上代码中,calendar首先会进行clear操作,然后进行set操作,在多线程情况下,set操作会覆盖之前的值,而且在后续对日期进行操作时,也可能会因为clear操作被清除导致异常

如何解决

  1. 将SimpleDateFormat定义成局部变量,每次使用时都new一个新对象,缺点就是频繁创建对象以及垃圾回收,影响系统性能
  2. 对方法加synchronized同步锁,缺点就是在高并发情况下性能较差
  3. 使用第三方库,比如joda-time,hutool等,将线程安全问题转移给第三方
  4. 使用ThreadLocal

使用ThreadLocal代码示例如下:

public class TestSimpleDateFormat {
    public static ThreadLocal<DateFormat> threadLocal = ThreadLocal.withInitial(
            () -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    );
    public static void main(String[] args) {
        for (int i=0; i<10; i++) {
            Thread thread = new Thread(() -> {
                try {
                    System.out.println(threadLocal.get().parse("2021-12-29 21:32:33"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
        }
    }
}

在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值