|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
什么时候上述的三种开发工具能和三为一,什么时候java的竞争力才更强,才有机会拉拢更多的程序员投入到对java的开发上,因为到时的开发工具将会比.net网页编程的更简单。还有一点也很关键,什么时候java推出的jsf能成为真正意义上的标准。想必人人对SimpleDateFormat其实不生疏。SimpleDateFormat是Java中一个十分经常使用的类,该类用来对日期字符串举行剖析和格局化输入,但假如利用不当心会招致十分奇妙和难以调试的成绩,由于DateFormat和SimpleDateFormat类不都是线程平安的,在多线程情况下挪用format()和parse()办法应当利用同步代码来制止成绩。上面我们经由过程一个详细的场景来一步步的深切进修和了解SimpleDateFormat类。
一.引子
我们都是优异的程序员,我们都晓得在程序中我们应该只管少的创立SimpleDateFormat实例,由于创立这么一个实例必要泯灭很年夜的价值。在一个读取数据库数据导出到excel文件的例子傍边,每次处置一个工夫信息的时分,就必要创立一个SimpleDateFormat实例对象,然后再抛弃这个对象。大批的对象就如许被创立出来,占用大批的内存和jvm空间。代码以下:
<br>- packagecom.peidasoft.dateformat;importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Date;publicclassDateUtil{publicstaticStringformatDate(Datedate)throwsParseException{SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");returnsdf.format(date);}publicstaticDateparse(StringstrDate)throwsParseException{SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");returnsdf.parse(strDate);}}
复制代码
<br>
你大概会说,OK,那我就创立一个静态的simpleDateFormat实例,然后放到一个DateUtil类(以下)中,在利用时间接利用这个实例举行操纵,如许成绩就办理了。改善后的代码以下:
<br>- packagecom.peidasoft.dateformat;importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Date;publicclassDateUtil{privatestaticfinalSimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");publicstaticStringformatDate(Datedate)throwsParseException{returnsdf.format(date);}publicstaticDateparse(StringstrDate)throwsParseException{returnsdf.parse(strDate);}}
复制代码
<br>
固然,这个办法切实其实很不错,在年夜部分的工夫内里城市事情得很好。但当你在临盆情况中利用一段工夫以后,你就会发明这么一个现实:它不是线程平安的。在一般的测试情形之下,都没有成绩,但一旦在临盆情况中必定负载情形下时,这个成绩就出来了。他会呈现各类分歧的情形,好比转化的工夫不准确,好比报错,好比线程被挂逝世等等。我们看上面的测试用例,那现实措辞:
<br>- packagecom.peidasoft.dateformat;importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Date;publicclassDateUtil{privatestaticfinalSimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");publicstaticStringformatDate(Datedate)throwsParseException{returnsdf.format(date);}publicstaticDateparse(StringstrDate)throwsParseException{returnsdf.parse(strDate);}}
复制代码
<br>
<br>- packagecom.peidasoft.dateformat;importjava.text.ParseException;importjava.util.Date;publicclassDateUtilTest{publicstaticclassTestSimpleDateFormatThreadSafeextendsThread{@Overridepublicvoidrun(){while(true){try{this.join(2000);}catch(InterruptedExceptione1){e1.printStackTrace();}try{System.out.println(this.getName()+":"+DateUtil.parse("2013-05-2406:02:20"));}catch(ParseExceptione){e.printStackTrace();}}}}publicstaticvoidmain(String[]args){for(inti=0;i<3;i++){newTestSimpleDateFormatThreadSafe().start();}}}
复制代码
<br>
实行输入以下:
<br>- Exceptioninthread"Thread-1"java.lang.NumberFormatException:multiplepointsatsun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1082)atjava.lang.Double.parseDouble(Double.java:510)atjava.text.DigitList.getDouble(DigitList.java:151)atjava.text.DecimalFormat.parse(DecimalFormat.java:1302)atjava.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)atjava.text.SimpleDateFormat.parse(SimpleDateFormat.java:1311)atjava.text.DateFormat.parse(DateFormat.java:335)atcom.peidasoft.orm.dateformat.DateNoStaticUtil.parse(DateNoStaticUtil.java:17)atcom.peidasoft.orm.dateformat.DateUtilTest$TestSimpleDateFormatThreadSafe.run(DateUtilTest.java:20)Exceptioninthread"Thread-0"java.lang.NumberFormatException:multiplepointsatsun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1082)atjava.lang.Double.parseDouble(Double.java:510)atjava.text.DigitList.getDouble(DigitList.java:151)atjava.text.DecimalFormat.parse(DecimalFormat.java:1302)atjava.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)atjava.text.SimpleDateFormat.parse(SimpleDateFormat.java:1311)atjava.text.DateFormat.parse(DateFormat.java:335)atcom.peidasoft.orm.dateformat.DateNoStaticUtil.parse(DateNoStaticUtil.java:17)atcom.peidasoft.orm.dateformat.DateUtilTest$TestSimpleDateFormatThreadSafe.run(DateUtilTest.java:20)Thread-2:MonMay2406:02:20CST2021Thread-2:FriMay2406:02:20CST2013Thread-2:FriMay2406:02:20CST2013Thread-2:FriMay2406:02:20CST2013
复制代码
<br>
申明:Thread-1和Thread-0报java.lang.NumberFormatException:multiplepoints毛病,间接挂逝世,没起来;Thread-2固然没有挂逝世,但输入的工夫是有毛病的,好比我们输出的工夫是:2013-05-2406:02:20,当会输入:MonMay2406:02:20CST2021如许的灵异事务。
二.缘故原由
作为一个专业程序员,我们固然都晓得,比拟于共享一个变量的开支要比每次创立一个新变量要小良多。下面的优化过的静态的SimpleDateFormat版,之地点并发情形下回呈现各类灵异毛病,是由于SimpleDateFormat和DateFormat类不是线程平安的。我们之以是无视线程平安的成绩,是由于从SimpleDateFormat和DateFormat类供应给我们的接口下去看,其实让人看不出它与线程平安有何干系。只是在JDK文档的最上面有以下申明:
SimpleDateFormat中的日期格局不是同步的。保举(倡议)为每一个线程创立自力的格局实例。假如多个线程同时会见一个格局,则它必需坚持内部同步。
JDK原始文档以下:
Synchronization:
Dateformatsarenotsynchronized.
Itisrecommendedtocreateseparateformatinstancesforeachthread.
Ifmultiplethreadsaccessaformatconcurrently,itmustbesynchronizedexternally.
上面我们经由过程看JDK源码来看看为何SimpleDateFormat和DateFormat类不是线程平安的真正缘故原由:
SimpleDateFormat承继了DateFormat,在DateFormat中界说了一个protected属性的Calendar类的对象:calendar。只是由于Calendar累的观点庞大,牵涉到时区与当地化等等,Jdk的完成中利用了成员变量来传送参数,这就形成在多线程的时分会呈现毛病。
在format办法里,有如许一段代码:
<br>- privateStringBufferformat(Datedate,StringBuffertoAppendTo,FieldDelegatedelegate){//Convertinputdatetotimefieldlistcalendar.setTime(date);booleanuseDateFormatSymbols=useDateFormatSymbols();for(inti=0;i<compiledPattern.length;){inttag=compiledPattern[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;default:subFormat(tag,count,delegate,toAppendTo,useDateFormatSymbols);break;}}returntoAppendTo;}
复制代码
<br>
calendar.setTime(date)这条语句改动了calendar,稍后,calendar还会用到(在subFormat办法里),而这就是激发成绩的本源。设想一下,在一个多线程情况下,有两个线程持有了统一个SimpleDateFormat的实例,分离挪用format办法:
线程1挪用format办法,改动了calendar这个字段。
中止来了。
线程2入手下手实行,它也改动了calendar。
又中止了。
线程1返来了,此时,calendar已然不是它所设的值,而是走上了线程2计划的路途。假如多个线程同时争抢calendar对象,则会呈现各类成绩,工夫不合错误,线程挂逝世等等。
剖析一下format的完成,我们不难发明,用到成员变量calendar,独一的优点,就是在挪用subFormat时,少了一个参数,却带来了这很多的成绩。实在,只需在这里用一个部分变量,一起传送下往,一切成绩都将水到渠成。
这个成绩面前埋没着一个更加主要的成绩--无形态:无形态办法的优点之一,就是它在各类情况下,都能够平安的挪用。权衡一个办法是不是是有形态的,就看它是不是修改了别的的工具,好比全局变量,好比实例的字段。format办法在运转过程当中修改了SimpleDateFormat的calendar字段,以是,它是有形态的。
这也同时提示我们在开辟和计划体系的时分注重下一下三点:
1.本人写公用类的时分,要对多线程挪用情形下的成果在正文里举行明白申明
2.对线程情况下,对每个共享的可变变量都要注重其线程平安性
3.我们的类和办法在做计划的时分,要只管计划成无形态的
三.办理举措
1.必要的时分创立新实例:
<br>- packagecom.peidasoft.dateformat;importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Date;publicclassDateUtil{publicstaticStringformatDate(Datedate)throwsParseException{SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");returnsdf.format(date);}publicstaticDateparse(StringstrDate)throwsParseException{SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");returnsdf.parse(strDate);}}
复制代码
<br>
申明:在必要用到SimpleDateFormat的中央新建一个实例,不论甚么时分,将有线程平安成绩的对象由共享变成部分公有都能制止多线程成绩,不外也减轻了创立对象的包袱。在一样平常情形下,如许实在对功能影响比不是很分明的。
2.利用同步:同步SimpleDateFormat对象
<br>- packagecom.peidasoft.dateformat;importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Date;publicclassDateSyncUtil{privatestaticSimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");publicstaticStringformatDate(Datedate)throwsParseException{synchronized(sdf){returnsdf.format(date);}}publicstaticDateparse(StringstrDate)throwsParseException{synchronized(sdf){returnsdf.parse(strDate);}}}
复制代码
<br>
申明:当线程较多时,当一个线程挪用该办法时,其他想要挪用此办法的线程就要block,多线程并发量年夜的时分会对功能有必定的影响。
3.利用ThreadLocal:
<br>- packagecom.peidasoft.dateformat;importjava.text.DateFormat;importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Date;publicclassConcurrentDateUtil{privatestaticThreadLocal<DateFormat>threadLocal=newThreadLocal<DateFormat>(){@OverrideprotectedDateFormatinitialValue(){returnnewSimpleDateFormat("yyyy-MM-ddHH:mm:ss");}};publicstaticDateparse(StringdateStr)throwsParseException{returnthreadLocal.get().parse(dateStr);}publicstaticStringformat(Datedate){returnthreadLocal.get().format(date);}}
复制代码
<br>
别的一种写法:
<br>- packagecom.peidasoft.dateformat;importjava.text.DateFormat;importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Date;publicclassThreadLocalDateUtil{privatestaticfinalStringdate_format="yyyy-MM-ddHH:mm:ss";privatestaticThreadLocal<DateFormat>threadLocal=newThreadLocal<DateFormat>();publicstaticDateFormatgetDateFormat(){DateFormatdf=threadLocal.get();if(df==null){df=newSimpleDateFormat(date_format);threadLocal.set(df);}returndf;}publicstaticStringformatDate(Datedate)throwsParseException{returngetDateFormat().format(date);}publicstaticDateparse(StringstrDate)throwsParseException{returngetDateFormat().parse(strDate);}}
复制代码
<br>
申明:利用ThreadLocal,也是将共享变质变为独享,线程独享一定能例如法独享在并发情况中能削减很多创立对象的开支。假如对功能请求对照高的情形下,一样平常保举利用这类办法。
4.丢弃JDK,利用其他类库中的工夫格局化类:
1.利用Apachecommons里的FastDateFormat,传播鼓吹是既快又线程平安的SimpleDateFormat,惋惜它只能对日期举行format,不克不及对日期串举行剖析。
不得不提一下的是:.net网页编程是看到java红,而开发出来的工具。 |
|