本篇主要讲解的是高并发下线程安全,使用的讲解例子是项目生产环境所发生的故障。
导致bug的代码简单说下,由于项目中的一个日期时间格式化的工具类,工具类内部使用SimpleDateFormat的无参构造器创建一个静态对象。在工具类的静态方法中,使用SimpleDateFormat的formatter.applyPattern进行格式化。类似下面的代码
1 | public static SimpleDateFormat formatter = new SimpleDateFormat(); |
在业务代码中使用,但是由于项目比较老,对接多方系统,导致系统中对于日期格式的转化并未统一,存在不同的格式的数据需要格式化。在高并发的情况下,直接导致部分日期解析不正确,并抛出异常
1 | java.lang.NumberFormatException: For input string: "" |
但是此问题并不容易重现,我们在对测试环境以及预发布环境都进行压测,并未重现此问题。
后续通过多方排查,才确认是SimpleDateFormat导致。
后经过整理,提出以下几种解决方案
方案一
写一个单独的方法,然后将 SimpleDateFormat
作为方法的成员变量,每个线程需要格式化时间的时候,就去调用这个方法,SimpleDateFormat
作为方法的成员变量,自然就不存在资源共享的问题了
1 | public static String getCurrentTime(String format) { |
方案二
使用==synchronized==,但是在高并发下会存在性能问题,亲测。
方案三
使用ThreadLocal将共享变量变为线程独享
示例代码:
1 | private static final Object lockerObj = new Object(); |
方案四
抛弃JDK,使用其他类库中的时间格式化类:推荐Apache commons 里的FastDateForma 以及 Joda-Time类库
而我们项目中使用的是Joda-Time,具体使用请自行查看官方文档