当前位置: 首页 > 科技观察

还在使用SimpleDateFormat吗?你的项目崩溃了吗?

时间:2023-03-23 11:09:41 科技观察

1。前言在日常开发中,我们经常需要用到与时间相关的类。说到时间相关的类,想必大家对SimpleDateFormat都不陌生。主要用于时间的格式化输出和分析,非常方便快捷,但SimpleDateFormat不是线程安全的类。在多线程的情况下,会出现异常,有经验的朋友一定遇到过。下面分析一下为什么SimpleDateFormat不安全?它是怎么发生的?还有SimpleDateFormat在多线程下的解决方案是什么?来看看《阿里巴巴开发手册》是怎么看待SimpleDateFormat的:公众号后台回复《阿里巴巴开发手册》得到《阿里巴巴开发手册》v1.4.02.问题场景复现一般我们在使用SimpleDateFormat的时候,定义为一个静态变量,避免频繁创建其对象实例,如下代码:StringstrateDate)throwsParseException{returnsdf.parse(strDate);}publicstaticvoidmain(String[]args)throwsInterruptedException,ParseException{System.out.println(sdf.format(newDate()));}}是不是觉得没啥问题用它?单线程自然没有什么问题,但是应用到多线程上就是大问题了。测试中:publicstaticvoidmain(String[]args)throwsInterruptedException,ParseException{ExecutorServiceservice=Executors.newFixedThreadPool(100);for(inti=0;i<20;i++){service.execute(()->{for(intj=0;j<10;j++){try{System.out.println(parse("2018-01-0209:45:59"));}catch(ParseExceptione){e.printStackTrace();}}});}//等待上面的线程执行完service.shutdown();service.awaitTermination(1,TimeUnit.DAYS);}控制台打印结果:你觉得这个没坏?部分线程获取时间不正确,部分线程直接报java.lang.NumberFormatException:multiplepointsiswrong,线程直接挂掉。3.多线程不安全的原因因为我们将SimpleDateFormat定义为静态变量,那么多线程下SimpleDateFormat的实例会被多个线程共享,B线程读取A线程的时间,并且会有时差等各种问题。SimpleDateFormat和它继承的DateFormat类也不是线程安全的来看看SimpleDateFormat的format()方法的源码//CalledfromFormataftercreatingaFieldDelegateprivateStringBufferformat(Datedate,StringBuffertoAppendTo,FieldDelegatedelegate){//Convertinputdatetotimefieldlistcalendar.setTime(date);booleanuseDateFormatSymbolsuseDateFormatSymbols=useDateFormatSymbols();for(inti=0;i>>8;intcount=compiledPattern[i++]&0xff;if(count==255){count=compiledPattern[i++]<<16;count|=compiledPattern[i++];}switch(tag){caseTAG_QUOTE_ASCII_CHAR:toAppendTo.append((char)count);break;caseTAG_QUOTE_CHARS:toAppendTo.append(compiledPattern,i,count);i+=count;break;默认:子格式(tag,count,delegate,toAppendTo,useDateFormatSymbols);break;}}returntoAppendTo;}注意,calendar.setTime(date),其实SimpleDateFormat的format方法是对Calendar进行操作的。因为我们将SimpleDateFormat声明为静态变量,所以它的Calendar变量也是一个共享变量,可以被多线程访问。假设线程A执行完calendar.setTime(date)并将时间设置为2019-01-02。此时挂起,线程B获得CPU执行权。线程B也执行到calendar.setTime(date),将时间设置为2019-01-03。线程挂了,线程A继续走,日历会继续使用(subFormat方法),但是此时日历使用的是线程B设置的值,这就是问题的根源,时间是错了,线程挂了等等等等。事实上,SimpleDateFormat源码的作者也给了我们一个提示:*Dateformatsarenotsynchronized*Itsrecommendedtocreateseparateformatinstancesforeachthread*Ifmultiplethreadsaccessingaformat,itmustbesynchronized*externally。这意味着日期格式不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问一种格式,则必须在外部同步该格式。4.解决方法是只在需要的时候才创建一个新的实例,不做静态修改returnsdf.format(date);}publicstaticDateparse(StringstrDate)throwsParseException{SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");returnsdf.parse(strDate);}如上面的代码,只创建一个新实例,其中需要用到,不存在线程安全问题,但是也增加了创建对象的负担,对象会频繁的创建和销毁,效率低下。同步大法好privatestaticfinalSimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");publicstaticStringformatDate(Datedate)throwsParseException{synchronized(sdf){returnsdf.format(date);}}publicstaticDateparse(StringstrDate)prowsParse{returnsdf.parse(strDate);}}简单粗暴。synchronized也可以解决线程安全问题。缺点是并发量大时,会影响性能,阻塞线程。ThreadLocalprivatestaticThreadLocalthreadLocal=newThreadLocal(){@OverrideprotectedDateFormatiinitialValue(){returnnewSimpleDateFormat("yyyy-MM-ddHH:mm:ss");}};publicstaticDateparse(StringdateStr)throwsParseException.get.parse(dateStr);}publicstaticStringformat(Datedate){returnthreadLocal.get().format(date);}ThreadLocal可以保证每个线程都能拿到一个SimpleDateFormat对象,自然不存在竞争问题。基于JDK1.8的DateTimeFormatter也是《阿里巴巴开发手册》给我们的解决方案,对之前的代码进行改造:{returnformatter.format(date);}publicstaticLocalDateTimeparse2(StringdateNow){returnLocalDateTime.parse(dateNow,formatter);}publicstaticvoidmain(String[]args)throwsInterruptedException,ParseException{ExecutorServiceservice=Executors.newFixedThreadPool(100);//20个线程(inti=0;i<20;i++){service.execute(()->{for(intj=0;j<10;j++){try{System.out.println(parse2(formatDate2(LocalDateTime.now())));}catch(Exceptione){e.printStackTrace();}}});}//等待上述线程执行完毕service.shutdown();service.awaitTermination(1,TimeUnit.DAYS);}}运行结果不会贴出来,不会有报错,时间不准的问题。DateTimeFormatter源代码的作者也评论说他的类是不可变的和线程安全的。*此类是不可变且线程安全的。

最新推荐
猜你喜欢