Dateformat在多线程下的异常问题
来源:互联网 发布:网络可能被劫持 编辑:程序博客网 时间:2024/06/16 11:37
SimpleDateFormat是我们经常使用的一个对日期字符串进行解析和格式化输出的类,但是使用不当会带来意想不到的问题。因为SimpleDateFormat中format()和parse()方法是线程不安全的,所以在多线程调用时会出现异常、转换不正确等问题,下面,我们来分析一下,
一般情况下,我们转换日期较多就会写一个通用Utils类来做时间转换与格式化,如下
class DateUtil{ public static Date parse(String str) throws ParseException { SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); return sim.parse(str); } public static String format(Date date){ SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); return sim.format(date); }}
但作为一名优秀程序员,我们才不允许在jvm中频繁创建SimpleDateFormat对象,所以我们会写成这样:
class DateUtil{ public static SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); public static Date parse(String str) throws ParseException { return sim.parse(str); } public static String format(Date date){ return sim.format(date); }}
下面我们来测试一下:
public class TestDateFormat { public static void main(String[] args) { for(int i=0; i<10000 ;i++){ String format = DateUtil.format(new Date()); new Thread(){ @Override public void run() { while (true){ try { this.join(2000); } catch (InterruptedException e) { e.printStackTrace(); } String format = DateUtil.format(new Date()); try { DateUtil.parse(format); } catch (ParseException e) { e.printStackTrace(); } } } }.start(); } }}
运行起来之后,其结果如下:
Exception in thread "Thread-7096" java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:601) at java.lang.Long.parseLong(Long.java:631) at java.text.DigitList.getLong(DigitList.java:195) at java.text.DecimalFormat.parse(DecimalFormat.java:2051) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at Javabase.DateUtil.parse(TestDateFormat.java:43) at Javabase.TestDateFormat$1.run(TestDateFormat.java:28)
那么问题来了 ,为什么会这样呢?
通过分析JDK源码,就会发现,jdk中有这么一段:
protected Calendar calendar;......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++]; } 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; }
这里的calendar对象会作为全局变量在format()中调用,而且会改变它的值,着我们就能想象得到:在一个多线程环境下,有两个线程持有了同一个SimpleDateFormat的实例,分别调用format方法:
线程1调用format方法,改变了calendar这个字段。
中断来了。
线程2开始执行,它也改变了calendar。
又中断了。
线程1回来了,此时,calendar已然不是它所设的值,而是走上了线程2设计的道路。如果多个线程同时争抢calendar对象,则会出现各种问题,时间不对,线程挂死等等。
那么,怎么解决这个问题呢,这里提供一些建议:
1、就是最开始那样:
class DateUtil{ public static Date parse(String str) throws ParseException { SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); return sim.parse(str); } public static String format(Date date){ SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); return sim.format(date); }}
这样无疑不会出现问题,但是每次都得创建新对象;
2、给SimpleDateFormat 加上snychroniaed:
class DateUtil{ public static SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); public static Date parse(String str) throws ParseException { synchronized(sim){ return sim.parse(str); } } public static String format(Date date){ synchronized(sim){ return sim.format(date); } }}
这就会比上面那个少创建对象,每次调用时都会检查SimpleDateFormat 对象是否在使用,故就不会出现同事调用的问题,而且性能比第一个好;
3、用TheardLocal<>修饰SimpleDateFormat :
class DateUtil{ private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static Date parse(String str) throws ParseException { return threadLocal.get().parse(str); } public static String format(Date date){ return threadLocal.get().format(date); }}
线程共享,就是在多线程下每个线程调用本线程内的SimpleDateFormat ,就不会出现上面问题。因为这个方式会在每个线程中都有一个SimpleDateFormat 实例,所以性能会比1和2都好,就是对象多了一点;
总结:
如果不要求性能就是用方法一,如果性能要求不大,就可以使用方法二,如果对想能要求很高,方法三可以解决很大问题
另外,还可以使用第三方来解决这个问题,Apache commons 里的FastDateFormat,宣称是既快又线程安全的SimpleDateFormat, 可惜它只能对日期进行format, 不能对日期串进行解析
- Dateformat在多线程下的异常问题
- DateFormat的多线程不安全
- 多线程环境下使用 DateFormat
- Linux下DateFormat的parse方法出现”ParseException”异常
- Linux下DateFormat的parse方法出现”ParseException”异常
- DateFormat在linux下系统和windows系统下取值不同的问题
- 常用集合在多线程下的问题
- SimpleDateFormat多线程下的异常
- VS2010在.net4框架下智能提示异常的问题
- Sqlite内存数据库在多线程下的使用问题
- 关于gethostbyname在多线程环境下的阻塞问题
- 关于gethostbyname在多线程环境下的阻塞问题
- JAVA的for each在多线程环境下问题
- [转]关于gethostbyname在多线程环境下的阻塞问题
- 关于gethostbyname在多线程环境下的阻塞问题
- 备忘:关于serialport 在多线程下的问题
- 智能指针在多线程情况下的问题
- 关于gethostbyname在多线程环境下的阻塞问题
- 客户流失预警(Predicting customer churn with scikit-learn)
- Android4.3 蓝牙BLE初步
- Android RTMP直播(续)
- C++ 数组引用
- 鸟哥私房菜:计算器概论
- Dateformat在多线程下的异常问题
- SQL注入攻击
- PHP中数组合并的两种方法及区别介绍
- Android开发必备工具AS安装使用教程
- CAN总线基础总结
- babel学习
- 元素内容 垂直居中
- Vue.js2.0从入门到放弃---入门实例(一)
- Ubuntu通过apt安装LAMP环境