a) 线程安全问题分析
为了找到问题所在,我们尝试查看 SimpleDateFormat
中 format
方法的源码来排查一下问题,format
源码如下:
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) { // 注意此行代码 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++]; } switch (tag) { case TAG_QUOTE_ASCII_CHAR: toAppendTo.append((char)count); break; case TAG_QUOTE_CHARS: toAppendTo.append(compiledPattern, i, count); i += count; break; default: subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols); break; } } return toAppendTo; }
从上述源码可以看出,在执行 SimpleDateFormat.format
方法时,会使用 calendar.setTime
方法将输入的时间进行转换,那么我们想想一下这样的场景:
- 线程 1 执行了
calendar.setTime(date)
方法,将用户输入的时间转换成了后面格式化时所需要的时间;
- 线程 1 暂停执行,线程 2 得到
CPU
时间片开始执行;
- 线程 2 执行了
calendar.setTime(date)
方法,对时间进行了修改;
- 线程 2 暂停执行,线程 1 得出
CPU
时间片继续执行,因为线程 1 和线程 2 使用的是同一对象,而时间已经被线程 2 修改了,所以此时当线程 1 继续执行的时候就会出现线程安全的问题了。
正常的情况下,程序的执行是这样的:
非线程安全的执行流程是这样的: