我们在对日期进行格式化时,除了用第三方的工具类,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 *** 作被清除导致异常
如何解决
- 将SimpleDateFormat定义成局部变量,每次使用时都new一个新对象,缺点就是频繁创建对象以及垃圾回收,影响系统性能
- 对方法加synchronized同步锁,缺点就是在高并发情况下性能较差
- 使用第三方库,比如joda-time,hutool等,将线程安全问题转移给第三方
- 使用ThreadLocal
使用ThreadLocal代码示例如下:
public class TestSimpleDateFormat { public static ThreadLocalthreadLocal = 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(); } } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)