1、最近生产环境遇到了一个问题:会员查询接口,会员返回的开始时间竟然比过期时间大。
vipStart=2018-08-07 18:37:28 vipEnd=2018-08-05 13:29:54
2、会员的开始时间是根据会员的过期时间来计算的(往前推31天)
查看系统日志,排除了数据库、代码的计算可能性后,决定用2018-08-07 18:37:28去查询一下日志,是否是线程切换造成的。
后面查询到了一个线程日志
vipStart=2018-07-07 18:37:28 vipEnd=2018-08-07 18:37:28
3、明显是线程不安全问题,后面查询了格式化工具:
public class DateUtils {
private static SimpleDateFormat simpleDateFormat = null;
method1(){}
method2(){}
method3(){}
}
(1)可以看到工具栏中static SimpleDateFormat是线程不安全的,当使用不同的pattern进行修改时,产生的日期格式可能不一样的。
(2)SimpleDateFormat,类内部有一个Calendar对象引用,它用来储存和这个sdf相关的日期信息,例如sdf.parse(dateStr), sdf.format(date) 诸如此类的方法参数传入的日期相关String, Date等等, 都是交由Calendar引用来储存的.这样就会导致一个问题,如果你的sdf是个static的, 那么多个thread 之间就会共享这个SimpleDateFormat, 同时也是共享这个Calendar引用, 并且, 观察 sdf.format() 方法,你会发现有如下的调用:
vipStart=2018-08-07 18:37:28 vipEnd=2018-08-05 13:29:54
2、会员的开始时间是根据会员的过期时间来计算的(往前推31天)
查看系统日志,排除了数据库、代码的计算可能性后,决定用2018-08-07 18:37:28去查询一下日志,是否是线程切换造成的。
后面查询到了一个线程日志
vipStart=2018-07-07 18:37:28 vipEnd=2018-08-07 18:37:28
3、明显是线程不安全问题,后面查询了格式化工具:
public class DateUtils {
private static SimpleDateFormat simpleDateFormat = null;
method1(){}
method2(){}
method3(){}
}
(1)可以看到工具栏中static SimpleDateFormat是线程不安全的,当使用不同的pattern进行修改时,产生的日期格式可能不一样的。
(2)SimpleDateFormat,类内部有一个Calendar对象引用,它用来储存和这个sdf相关的日期信息,例如sdf.parse(dateStr), sdf.format(date) 诸如此类的方法参数传入的日期相关String, Date等等, 都是交由Calendar引用来储存的.这样就会导致一个问题,如果你的sdf是个static的, 那么多个thread 之间就会共享这个SimpleDateFormat, 同时也是共享这个Calendar引用, 并且, 观察 sdf.format() 方法,你会发现有如下的调用:
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition pos)
{
pos.beginIndex = pos.endIndex = 0;
return format(date, toAppendTo, pos.getFieldDelegate());
}
// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
boolean useDateFormatSymbols = useDateFormatSymbols();
for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
}
4、替代方案:
(1)将工具类中private static SimpleDateFormat simpleDateFormat = null;去除掉,写到方法里面去;
(2)使用threadlocal方式;
(3)使用Apache下面的DateFormatUtils、DateUtils、最主要的区别是线程安全、占用内存少。(推荐)
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang.time.DateUtils;附图:SimpleDateFormat与DateFormatUtils、DateUtils占用的内存,可以看出apache工具类的优势。

本文描述了一种在生产环境中遇到的会员查询接口时间逻辑错误,即会员开始时间晚于过期时间的问题。通过分析线程日志,发现该问题是由于线程不安全的SimpleDateFormat工具类引起,并给出了三种解决方案。
2459

被折叠的 条评论
为什么被折叠?



