面试官一步一步的套路你,为什么 SimpleDateFormat 不是线程安全的?

栏目: IT技术 · 发布时间: 4年前

面试官 :SimpleDateFormat用过吗?

小小白 :用过,用来格式化或解析日期时间。

面试官 :能写一下你是如何使用的吗?

小小白 :噼里啪啦敲完了,代码如下:

public class SimpleDateFormatTest {

static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {

@Override

protected DateFormat initialValue() {

return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

}

};



public static void main(String[] args) {

try {

df.get().parse("2020-03-27 10:09:01");

} catch (ParseException e) {

e.printStackTrace();

} finally {

df.remove();

}

}

}

面试官 :为什么要使用ThreadLocal包装SimpleDateFormat?

小小白 :如果不使用ThreadLocal包装一下,直接创建一个SimpleDateFormat共享实例对象,在多线程并发的情况下使用这个对象的方法是线程不安全的,可能会抛出NumberFormatException或其它异常。使用ThreadLocal包装一下,每个线程都有自己的SimpleDateFormat实例对象,这样多线程并发的情况下就不会出现线程不安全的问题了。

面试官 :那为什么SimpleDateFormat作为共享变量会出现线程不安全的问题?

小小白 :以下面的代码为例,说明一下原因。

public class SimpleDateFormatTest {

private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");

public static void main(String[] args) {

for (int i = 0; i <= 15; i++) {

new Thread(new Runnable() {

@Override

public void run() {

try {

System.out.println(sdf.parse("2020-3-27 10:09:01"));

} catch (Exception e) {

e.printStackTrace();

}

}

}).start();

}

}

}

SimpleDateFormat类继承了DateFormat抽象类,在DateFormat抽象类中有一个Calendar类型的属性calendar,它就是导致SimpleDateFormat线程不安全的关键。上面代码中变量sdf是全局共享的,首先通过SimpleDateFormat构造方法创建对象,进入这个构造方法的源码。

public SimpleDateFormat(String pattern){

this(pattern, Locale.getDefault(Locale.Category.FORMAT));

}

public SimpleDateFormat(String pattern, Locale locale){

if (pattern == null || locale == null) {

throw new NullPointerException();

}

// 初始化一个Calendar实例

initializeCalendar(locale);

this.pattern = pattern;

this.formatData = DateFormatSymbols.getInstanceRef(locale);

this.locale = locale;

initialize(locale);

}


// 如果DateFormat中calendar属性等于null,则创建一个

private void initializeCalendar(Locale loc) {

if (calendar == null) {

assert loc != null;

// The format object must be constructed using the symbols for this zone.

// However, the calendar should use the current default TimeZone.

// If this is not contained in the locale zone strings, then the zone

// will be formatted using generic GMT+/-H:MM nomenclature.

calendar = Calendar.getInstance(TimeZone.getDefault(), loc);

}

}

在这个构造方法中初始化了一些SimpleDateFormat的属性值,特别是calendar。接着,进入SimpleDateFormat类的parse方法源码。

public Date parse(String source) throws ParseException

{

ParsePosition pos = new ParsePosition(0);

// 通过参数值source解析得到一个Date

Date result = parse(source, pos);

if (pos.index == 0)

throw new ParseException("Unparseable date: \"" + source + "\"" ,

pos.errorIndex);

return result;

}

// 通过参数值source解析得到一个Date

// 这个方法代码很多,这里省略不重要的代码

public Date parse(String text, ParsePosition pos)

{

// 省略解析日期字符串的部分代码

CalendarBuilder calb = new CalendarBuilder();

// 日期字符串解析完成后存放到calb中

Date parsedDate;

try {

// 使用calb中解析好的日期数据设置calendar

parsedDate = calb.establish(calendar).getTime();

// 省略部分代码

}

catch (IllegalArgumentException e) {

// 省略部分代码

}

return parsedDate;

}


// CalendarBuilder类的establish方法部分代码

Calendar establish(Calendar cal) {

// 省略部分不重要代码

cal.clear();

// 省略部分不重要代码

return cal;

}

从上面的代码可以看到,根据传入的日期时间字符串来解析,然后将解析好的日期数据设置到calendar中,也就是通过establish方法完成的,注意看这个方法中调用了cal.clear(),这将会导致calendar中的属性值变为初始值,如果在多线程并发的情况下,有可能线程A刚执行完establish方法,线程B就执行了cal.clear(),导致最终的解析异常。

面试官 :那既然是因为SimpleDateFormat类中的calendar是共享变量导致的,可以将SimpleDateFormat类型的变量设置成方法私有的,每次要用的时候new一个不就完了,或者使用同步锁控制一下并发访问,为什么还要使用ThreadLocal这么麻烦?

小小白 :方法私有和同步锁并发控制确实可以解决问题,但是他们有各自的缺点。方法私有,每次都需要创建一个新对象,这样占用内存不说,还需要不断回收。同步锁并发控制,多线程高并发的情况下,性能会严重下降。使用ThreadLocal包装,每个线程只需要创建一个SimpleDateFormat对象实例,既解决不断创建新对象的问题,又解决了并发的问题。

面试官 :那为什么最开始你写的代码中,在finally方法中使用了df.remove(),这是为什么?

小小白 :线程用完了SimpleDateFormat,如果不调用remove方法将其清除,可能会引发因使用ThreadLocal而导致的内存泄漏。

面试官 :使用ThreadLocal为什么可能会导致内存泄漏?

请点击阅读 《都说ThreadLocal被面试官问烂了,可为什么面试官还是喜欢继续问》

推荐阅读

都说ThreadLocal被面试官问烂了,可为什么面试官还是喜欢继续问

Java注解是如何玩转的,面试官和我聊了半个小时

如何去除代码中的多次if而引发的一连串面试问题

String引发的提问,我差点跪了

就写了一行代码,被问了这么多问题

面试官:JVM对锁进行了优化,都优化了啥?

synchronized连环问

三分钟快速搞定git常规使用

面试官一步一步的套路你,为什么 SimpleDateFormat 不是线程安全的?

点点" 在看 "   


以上所述就是小编给大家介绍的《面试官一步一步的套路你,为什么 SimpleDateFormat 不是线程安全的?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Design and Analysis of Distributed Algorithms (Wiley Series on P

Design and Analysis of Distributed Algorithms (Wiley Series on P

Nicola Santoro / Wiley-Interscience / 2006-10-27 / USD 140.95

This text is based on a simple and fully reactive computational model that allows for intuitive comprehension and logical designs. The principles and techniques presented can be applied to any distrib......一起来看看 《Design and Analysis of Distributed Algorithms (Wiley Series on P》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试