解决SimpleDateFormat的线程不安全问题的方法

编程技术  /  houtizong 发布于 3年前   96

在Java项目中,我们通常会自己写一个DateUtil类,处理日期和字符串的转换,如下所示:

public class DateUtil01 {private SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public void format(Date date) {System.out.println(dateformat.format(date));}public void parse(String str) {try {System.out.println(dateformat.parse(str));} catch (ParseException e) {e.printStackTrace();}}}

 然而,由于SimpleDateFormat类不是线程安全的,所以在多线程的环境下,往往会出现意想不到的结果。
 如下是我写的测试其是否存在安全问题的实例:

1.日期工具处理类的接口

package com.bijian.study.date;import java.util.Date;public interface DateUtilInterface {public void format(Date date);public void parse(String str);}

 

2.日期工具实现类

package com.bijian.study.date;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;public class DateUtil01 implements DateUtilInterface {private SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");@Overridepublic void format(Date date) {System.out.println(dateformat.format(date));}@Overridepublic void parse(String str) {try {System.out.println(dateformat.parse(str));} catch (ParseException e) {e.printStackTrace();}}}

 

3.调用日期工具的线程类

package com.bijian.study.date;import java.util.Calendar;import java.util.Date;public class DateThread implements Runnable {DateUtilInterface dateUtil = null;public DateThread(DateUtilInterface dateUtil) {this.dateUtil = dateUtil;}public void run() {int year = 2000;Calendar cal;for (int i = 1; i < 100; i++) {System.out.println("no." + i);year++;cal = Calendar.getInstance();cal.set(Calendar.YEAR, year);//Date date = cal.getTime();//dateUtil.format(date);dateUtil.parse(year + "-05-25 11:21:21");try {Thread.sleep(1);} catch (Exception e) {e.printStackTrace();}}}}

 

4.测试主方法

package com.bijian.study.date;public class DateMainTest {public static void main(String[] args) {DateUtilInterface dateUtil = new DateUtil01();Runnable runabble = new DateThread(dateUtil);for(int i=0;i<10;i++){            new Thread(runabble).start();}}}

 

运行结果:

no.1no.1no.1Fri May 25 11:21:21 CST 2001Fri May 25 11:21:21 CST 2001Fri May 25 11:21:21 CST 2001no.1no.1Fri May 25 11:21:21 CST 2001Fri May 25 11:21:21 CST 2001no.1no.1Fri May 25 11:21:21 CST 2001no.1Fri May 25 11:21:21 CST 2001no.1Fri May 25 11:00:21 CST 2001Wed Sep 25 11:21:21 CST 2002no.1no.2no.2Sat May 25 11:21:21 CST 2002no.2no.2no.2Sat May 25 11:21:21 CST 2002no.2Sat May 25 11:21:21 CST 2002Fri May 25 11:21:21 CST 2001Sat May 25 11:21:21 CST 2002no.2Sat May 25 11:21:21 CST 2002no.2Sat May 25 11:21:21 CST 2002no.2Sat May 25 11:21:21 CST 2002Exception in thread "Thread-2" java.lang.NumberFormatException: For input string: ".00221E.00221E4"at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)at java.lang.Double.parseDouble(Unknown Source)at java.text.DigitList.getDouble(Unknown Source)at java.text.DecimalFormat.parse(Unknown Source)at java.text.SimpleDateFormat.subParse(Unknown Source)at java.text.SimpleDateFormat.parse(Unknown Source)at java.text.DateFormat.parse(Unknown Source)at com.bijian.study.date.DateUtil01.parse(DateUtil01.java:19)at com.bijian.study.date.DateThread.run(DateThread.java:24)at java.lang.Thread.run(Unknown Source)Exception in thread "Thread-5" java.lang.NumberFormatException: For input string: ".00221E.00221E44"at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)at java.lang.Double.parseDouble(Unknown Source)at java.text.DigitList.getDouble(Unknown Source)at java.text.DecimalFormat.parse(Unknown Source)at java.text.SimpleDateFormat.subParse(Unknown Source)at java.text.SimpleDateFormat.parse(Unknown Source)at java.text.DateFormat.parse(Unknown Source)at com.bijian.study.date.DateUtil01.parse(DateUtil01.java:19)at com.bijian.study.date.DateThread.run(DateThread.java:24)at java.lang.Thread.run(Unknown Source)no.3no.3Sun May 25 11:21:21 CST 2003no.3no.3no.3Sun May 25 11:21:21 CST 2003no.4Sun May 25 11:21:21 CST 2003no.3Tue May 25 11:21:21 CST 2004no.2Sun May 25 11:21:21 CST 2003no.3Thu Jan 01 00:21:21 CST 1970Exception in thread "Thread-7" Exception in thread "Thread-0" java.lang.NumberFormatException: For input string: "E212"at java.lang.NumberFormatException.forInputString(Unknown Source)at java.lang.Long.parseLong(Unknown Source)at java.lang.Long.parseLong(Unknown Source)at java.text.DigitList.getLong(Unknown Source)at java.text.DecimalFormat.parse(Unknown Source)at java.text.SimpleDateFormat.subParse(Unknown Source)at java.text.SimpleDateFormat.parse(Unknown Source)at java.text.DateFormat.parse(Unknown Source)at com.bijian.study.date.DateUtil01.parse(DateUtil01.java:19)at com.bijian.study.date.DateThread.run(DateThread.java:24)at java.lang.Thread.run(Unknown Source)java.lang.NumberFormatException: For input string: "E212"at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)at java.lang.Double.parseDouble(Unknown Source)at java.text.DigitList.getDouble(Unknown Source)at java.text.DecimalFormat.parse(Unknown Source)at java.text.SimpleDateFormat.subParse(Unknown Source)at java.text.SimpleDateFormat.parse(Unknown Source)at java.text.DateFormat.parse(Unknown Source)at com.bijian.study.date.DateUtil01.parse(DateUtil01.java:19)at com.bijian.study.date.DateThread.run(DateThread.java:24)at java.lang.Thread.run(Unknown Source)Exception in thread "Thread-8" java.lang.NumberFormatException: For input string: ""at java.lang.NumberFormatException.forInputString(Unknown Source)at java.lang.Long.parseLong(Unknown Source)at java.lang.Long.parseLong(Unknown Source)at java.text.DigitList.getLong(Unknown Source)at java.text.DecimalFormat.parse(Unknown Source)at java.text.SimpleDateFormat.subParse(Unknown Source)at java.text.SimpleDateFormat.parse(Unknown Source)at java.text.DateFormat.parse(Unknown Source)at com.bijian.study.date.DateUtil01.parse(DateUtil01.java:19)at com.bijian.study.date.DateThread.run(DateThread.java:24)at java.lang.Thread.run(Unknown Source)no.4no.4.........

 

      从如上运行结果来看,SimpleDateFormatparse方法有线程安全问题。

   修改调用日期工具的线程类如下,测试SimpleDateFormat的format方法是否有线程安全问题

package com.bijian.study.date;import java.util.Calendar;import java.util.Date;public class DateThread implements Runnable {DateUtilInterface dateUtil = null;public DateThread(DateUtilInterface dateUtil) {this.dateUtil = dateUtil;}public void run() {int year = 2000;Calendar cal;for (int i = 1; i < 100; i++) {System.out.println("no." + i);year++;cal = Calendar.getInstance();cal.set(Calendar.YEAR, year);Date date = cal.getTime();dateUtil.format(date);//dateUtil.parse(year + "-05-25 11:21:21");try {Thread.sleep(1);} catch (Exception e) {e.printStackTrace();}}}}

 运行结果:

no.1no.12001-05-22 13:07:222001-05-22 13:07:22no.1no.12001-05-22 13:07:222001-05-22 13:07:22no.12001-05-22 13:07:22no.1no.12001-05-22 13:07:222001-05-22 13:07:22no.1no.12001-05-22 13:07:222001-05-22 13:07:22no.12001-05-22 13:07:22no.2no.2no.2no.22002-05-22 13:07:22no.22002-05-22 13:07:222002-05-22 13:07:22no.22002-05-22 13:07:22no.2.........

    多次运行,均未出现异常,因此个人预测,SimpleDateFormat的format方法没有线程安全的问题。 

      

        有三种方法可以解决以上安全问题。
  1).使用同步

package com.bijian.study.date;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;public class DateUtil02 implements DateUtilInterface {private SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");@Overridepublic void format(Date date) {System.out.println(dateformat.format(date));}@Overridepublic void parse(String str) {try {synchronized(dateformat){System.out.println(dateformat.parse(str));}} catch (ParseException e) {e.printStackTrace();}}}

 修改DateMainTest.java的DateUtilInterface dateUtil = new DateUtil01();为DateUtilInterface dateUtil = new DateUtil02();测试OK。

        不过,当线程较多时,当一个线程调用该方法时,其他想要调用此方法的线程就要block,这样的操作也会一定程度上影响性能。

 

  2).每次使用时,都创建一个新的SimpleDateFormat实例

package com.bijian.study.date;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;public class DateUtil03 implements DateUtilInterface {@Overridepublic void format(Date date) {SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(dateformat.format(date));}@Overridepublic void parse(String str) {try {SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(dateformat.parse(str));} catch (ParseException e) {e.printStackTrace();}}}

 修改DateMainTest.java的DateUtilInterface dateUtil = new DateUtil02();为DateUtilInterface dateUtil = new DateUtil03();测试OK。

  

  3).借助ThreadLocal对象每个线程只创建一个实例

     借助ThreadLocal对象每个线程只创建一个实例,这是推荐的方法

     对于每个线程SimpleDateFormat不存在影响他们之间协作的状态,为每个线程创建一个SimpleDateFormat变量的拷贝或者叫做副本,代码如下:

package com.bijian.study.date;import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;public class DateUtil04 implements DateUtilInterface {private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";// 第一次调用get将返回nullprivate static ThreadLocal threadLocal = new ThreadLocal(){protected Object initialValue() {  return null;//直接返回null  } };// 获取线程的变量副本,如果不覆盖initialValue,第一次get返回null,故需要初始化一个SimpleDateFormat,并set到threadLocal中public static DateFormat getDateFormat() {DateFormat df = (DateFormat) threadLocal.get();if (df == null) {df = new SimpleDateFormat(DATE_FORMAT);threadLocal.set(df);}return df;}@Overridepublic void parse(String textDate) {try {System.out.println(getDateFormat().parse(textDate));} catch (ParseException e) {e.printStackTrace();}}@Overridepublic void format(Date date) {System.out.println(getDateFormat().format(date));}}

 创建一个ThreadLocal类变量,这里创建时用了一个匿名类,覆盖了initialValue方法,主要作用是创建时初始化实例。

         也可以采用下面方式创建。

package com.bijian.study.date;import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;public class DateUtil05 implements DateUtilInterface {private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";@SuppressWarnings("rawtypes")private static ThreadLocal threadLocal = new ThreadLocal() {protected synchronized Object initialValue() {return new SimpleDateFormat(DATE_FORMAT);}};public DateFormat getDateFormat() {return (DateFormat) threadLocal.get();}@Overridepublic void parse(String textDate) {try {System.out.println(getDateFormat().parse(textDate));} catch (ParseException e) {e.printStackTrace();}}@Overridepublic void format(Date date) {System.out.println(getDateFormat().format(date));}}

    修改DateMainTest.java的DateUtilInterface dateUtil = new DateUtil03();为DateUtilInterface dateUtil = new DateUtil04();或者DateUtilInterface dateUtil = new DateUtil05();测试OK。

 

      最后,当然也可以使用apache commons-lang包的DateFormatUtils或者FastDateFormat实现,apache保证是线程安全的,并且更高效。

 

     附:Oracle官方bug说明: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6178997

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!

留言需要登陆哦

技术博客集 - 网站简介:
前后端技术:
后端基于Hyperf2.1框架开发,前端使用Bootstrap可视化布局系统生成

网站主要作用:
1.编程技术分享及讨论交流,内置聊天系统;
2.测试交流框架问题,比如:Hyperf、Laravel、TP、beego;
3.本站数据是基于大数据采集等爬虫技术为基础助力分享知识,如有侵权请发邮件到站长邮箱,站长会尽快处理;
4.站长邮箱:[email protected];

      订阅博客周刊 去订阅

文章归档

文章标签

友情链接

Auther ·HouTiZong
侯体宗的博客
© 2020 zongscan.com
版权所有ICP证 : 粤ICP备20027696号
PHP交流群 也可以扫右边的二维码
侯体宗的博客