湖南教育平台网站建设查公司注册信息怎么查
news/
2025/9/23 21:21:31/
文章来源:
湖南教育平台网站建设,查公司注册信息怎么查,黑色asp企业网站源码,广告制作费转载自 关于SimpleDateFormat时间格式化线程安全问题昨天推送的文章《关于创建和销毁对象》一文中#xff0c;2.1重复利用对象这一小节所举的SimpleDateFormat格式化时间的例子是不合适的#xff0c;因为多线程场景下#xff0c;SimpleDateFormat存在线程安全问题。在此2.1重复利用对象这一小节所举的SimpleDateFormat格式化时间的例子是不合适的因为多线程场景下SimpleDateFormat存在线程安全问题。在此特别感谢利群、Simon、Aaron等几位同学认真指出。想必大家对SimpleDateFormat并不陌生。SimpleDateFormat 是 Java 中一个非常常用的类该类用来对日期字符串进行解析和格式化输出但如果使用不小心会导致非常微妙和难以调试的问题因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的在多线程环境下调用 format() 和 parse() 方法应该使用同步代码来避免问题。下面我们通过一个具体的场景来一步步的深入学习和理解SimpleDateFormat类。
一.引子
首先我们都知道在程序中我们应当尽量少的创建SimpleDateFormat 实例因为创建这么一个实例需要耗费很大的代价。在一个读取数据库数据导出到excel文件的例子当中每次处理一个时间信息的时候就需要创建一个SimpleDateFormat实例对象然后再丢弃这个对象。大量的对象就这样被创建出来占用大量的内存和 jvm空间。代码如下
public class DateUtil {public static String formatDate(Date date){SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);return sdf.format(date);}public static Date parse(String strDate) throws ParseException{SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);return sdf.parse(strDate);}}
你也许会说OK那我就创建一个静态的simpleDateFormat实例然后放到一个DateUtil类如下中在使用时直接使用这个实例进行操作这样问题就解决了。改进后的代码如下
public class DateUtil {private static final SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);public static String formatDate(Date date){return sdf.format(date);}public static Date parse(String strDate) throws ParseException {return sdf.parse(strDate);}
}
当然这个方法的确很不错在大部分的时间里面都会工作得很好。但当你在生产环境中使用一段时间之后你就会发现这么一个事实它不是线程安全的。在正常的测试情况之下都没有问题但一旦在生产环境中一定负载情况下时这个问题就出来了。他会出现各种不同的情况比如转化的时间不正确比如报错比如线程被挂死等等。我们看下面的测试用例拿事实说话
public class DateUtilTest {public static class SimpleDateFormatThreadSafeTest extends Thread {Overridepublic void run() {while (true) {try {this.join(2000);} catch (InterruptedException e1) {e1.printStackTrace();}try {System.out.println(this.getName() : DateUtil.parse(2016-09-13 22:00:00));} catch (ParseException e) {e.printStackTrace();}}}}public static void main(String[] args) {for(int i 0; i 3; i){new SimpleDateFormatThreadSafeTest().start();}}
}
执行输出如下
Exception in thread Thread-2 Exception in thread Thread-0 java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1110) at java.lang.Double.parseDouble(Double.java:540) at java.text.DigitList.getDouble(DigitList.java:168) at java.text.DecimalFormat.parse(DecimalFormat.java:1321) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1793) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455) at java.text.DateFormat.parse(DateFormat.java:355) at com.shengfei.test.DateFormat.DateUtil.parse(DateUtil.java:31) at com.shengfei.test.DateFormat.DateUtilTest$SimpleDateFormatThreadSafeTest.run(DateUtilTest.java:32)java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1110) at java.lang.Double.parseDouble(Double.java:540) at java.text.DigitList.getDouble(DigitList.java:168) at java.text.DecimalFormat.parse(DecimalFormat.java:1321) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1793) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455) at java.text.DateFormat.parse(DateFormat.java:355) at com.shengfei.test.DateFormat.DateUtil.parse(DateUtil.java:31) at com.shengfei.test.DateFormat.DateUtilTest$SimpleDateFormatThreadSafeTest.run(DateUtilTest.java:32)Thread-1:Mon Sep 13 22:00:00 CST 2202Thread-1:Tue Sep 13 22:00:00 CST 2016Thread-1:Tue Sep 13 22:00:00 CST 2016Thread-1:Tue Sep 13 22:00:00 CST 2016说明Thread-2和Thread-0报java.lang.NumberFormatException: multiple points错误直接挂死没起来Thread-1虽然没有挂死但输出的时间是有错误的比如我们输入的时间是2016-09-13 22:00:00 当会输出Mon Sep 13 22:00:00 CST 2202 这样的灵异事件。
二.原因
我们都知道相比于共享一个变量的开销要比每次创建一个新变量要小很多。上面的优化过的静态的SimpleDateFormat版之所在并发情况下回出现各种灵异错误是因为SimpleDateFormat和DateFormat类不是线程安全的。我们之所以忽视线程安全的问题是因为从SimpleDateFormat和DateFormat类提供给我们的接口上来看实在让人看不出它与线程安全有何相干。只是在JDK文档的最下面有如下说明
SynchronizationDate formats are not synchronized.
It is recommended to create separate format instances for each thread.
If multiple threads access a format concurrently, it must be synchronized externally.
翻译一下就是SimpleDateFormat中的日期格式不是同步的。建议为每个线程创建独立的格式实例。如果多个线程同时访问一个格式则它必须保持外部同步。
下面我们通过看JDK源码来看看为什么SimpleDateFormat和DateFormat类不是线程安全的真正原因。
SimpleDateFormat继承了DateFormat,在DateFormat中定义了一个protected属性的 Calendar类的对象calendar。只是因为Calendar类的概念复杂牵扯到时区与本地化等等Jdk的实现中使用了成员变量来传递参数这就造成在多线程的时候会出现错误。
在format方法里有这样一段代码
private StringBuffer format(Date date, StringBuffer toAppendTo,FieldDelegate delegate) {// Convert input date to time field listcalendar.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.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.需要的时候创建新实例
public class DateUtil {public static String formatDate(Date date){SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);return sdf.format(date);}public static Date parse(String strDate) throws ParseException {SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);return sdf.parse(strDate);}
}
说明在需要用到SimpleDateFormat 的地方新建一个实例不管什么时候将有线程安全问题的对象由共享变为局部私有都能避免多线程问题不过也加重了创建对象的负担。在一般情况下这样其实对性能影响比不是很明显的。
2.使用同步同步SimpleDateFormat对象
public class DateUtil {private static SimpleDateFormat sdf2 new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);public static String formatDate2(Date date){synchronized(sdf2){return sdf2.format(date);}}public static Date parse2(String strDate) throws ParseException{synchronized(sdf2){return sdf2.parse(strDate);}}
}
说明当线程较多时当一个线程调用该方法时其他想要调用此方法的线程就要block多线程并发量大的时候会对性能有一定的影响。
3.使用ThreadLocal
public class ConcurrentDateUtil {private static ThreadLocalDateFormat threadLocal new ThreadLocalDateFormat() {Overrideprotected DateFormat initialValue() {return new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);}};public static Date parse(String dateStr) throws ParseException {return threadLocal.get().parse(dateStr);}public static String format(Date date) {return threadLocal.get().format(date);}
}
另外一种写法
public class ThreadLocalDateUtil {private static final String date_format yyyy-MM-dd HH:mm:ss;private static ThreadLocalDateFormat threadLocal new ThreadLocalDateFormat();public static DateFormat getDateFormat(){DateFormat df threadLocal.get();if(dfnull){df new SimpleDateFormat(date_format);threadLocal.set(df);}return df;}public static String formatDate(Date date){return getDateFormat().format(date);}public static Date parse(String strDate) throws ParseException {return getDateFormat().parse(strDate);}}
说明使用ThreadLocal, 也是将共享变量变为独享线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。如果对性能要求比较高的情况下一般推荐使用这种方法。
4.抛弃JDK使用其他类库中的时间格式化类
1使用Apache commons 里的FastDateFormat宣称是既快又线程安全的SimpleDateFormat, 可惜它只能对日期进行format, 不能对日期串进行解析。
2使用Joda-Time类库来处理时间相关问题。
做一个简单的压力测试方法1最慢方法3最快但是就算是最慢的方法一性能也不差一般系统方法1和方法2就可以满足所以说在这个点很难成为你系统的瓶颈所在。从简单的角度来说建议使用方法1或者方法2如果在必要的时候追求那么一点性能提升的话可以考虑用方法3用ThreadLocal做缓存。
Joda-Time类库对时间处理方式比较完美建议使用。
参考资料
1.http://dreamhead.blogbus.com/logs/215637834.html
2.http://www.blogjava.net/killme2008/archive/2011/07/10/354062.html
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/913921.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!