在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"); @Override public void format(Date date) { System.out.println(dateformat.format(date)); } @Override public 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.1 no.1 no.1 Fri May 25 11:21:21 CST 2001 Fri May 25 11:21:21 CST 2001 Fri May 25 11:21:21 CST 2001 no.1 no.1 Fri May 25 11:21:21 CST 2001 Fri May 25 11:21:21 CST 2001 no.1 no.1 Fri May 25 11:21:21 CST 2001 no.1 Fri May 25 11:21:21 CST 2001 no.1 Fri May 25 11:00:21 CST 2001 Wed Sep 25 11:21:21 CST 2002 no.1 no.2 no.2 Sat May 25 11:21:21 CST 2002 no.2 no.2 no.2 Sat May 25 11:21:21 CST 2002 no.2 Sat May 25 11:21:21 CST 2002 Fri May 25 11:21:21 CST 2001 Sat May 25 11:21:21 CST 2002 no.2 Sat May 25 11:21:21 CST 2002 no.2 Sat May 25 11:21:21 CST 2002 no.2 Sat May 25 11:21:21 CST 2002 Exception 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.3 no.3 Sun May 25 11:21:21 CST 2003 no.3 no.3 no.3 Sun May 25 11:21:21 CST 2003 no.4 Sun May 25 11:21:21 CST 2003 no.3 Tue May 25 11:21:21 CST 2004 no.2 Sun May 25 11:21:21 CST 2003 no.3 Thu Jan 01 00:21:21 CST 1970 Exception 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.4 no.4 ... ... ...
从如上运行结果来看,SimpleDateFormat的parse方法有线程安全问题。
修改调用日期工具的线程类如下,测试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.1 no.1 2001-05-22 13:07:22 2001-05-22 13:07:22 no.1 no.1 2001-05-22 13:07:22 2001-05-22 13:07:22 no.1 2001-05-22 13:07:22 no.1 no.1 2001-05-22 13:07:22 2001-05-22 13:07:22 no.1 no.1 2001-05-22 13:07:22 2001-05-22 13:07:22 no.1 2001-05-22 13:07:22 no.2 no.2 no.2 no.2 2002-05-22 13:07:22 no.2 2002-05-22 13:07:22 2002-05-22 13:07:22 no.2 2002-05-22 13:07:22 no.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"); @Override public void format(Date date) { System.out.println(dateformat.format(date)); } @Override public 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 { @Override public void format(Date date) { SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(dateformat.format(date)); } @Override public 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将返回null private 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; } @Override public void parse(String textDate) { try { System.out.println(getDateFormat().parse(textDate)); } catch (ParseException e) { e.printStackTrace(); } } @Override public 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(); } @Override public void parse(String textDate) { try { System.out.println(getDateFormat().parse(textDate)); } catch (ParseException e) { e.printStackTrace(); } } @Override public 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
相关推荐
在Java编程语言中,`SimpleDateFormat`类是一个广泛使用的日期时间格式化工具,但它的线程安全性是一个常常被开发者忽视的问题。标题指出的"simpleDateFormat是线程不安全的",意味着在多线程环境下,如果多个线程...
目录SimpleDateFormat诡异bug复现SimpleDateFormat诡异bug字符串日期转...方法时加锁使用ThreadLocalThreadLocal介绍ThreadLocal使用demoThreadLocal源码探索ThreadLocal注意事项使用ThreadLocal解决SimpleDateFormat...
要解决SimpleDateFormat类的线程安全问题,有多种方法可以选择: 1. 使用ThreadLocal: 可以使用ThreadLocal将SimpleDateFormat对象封装在ThreadLocal中,这样每个线程都有自己的SimpleDateFormat对象,从而避免了...
SimpleDateFormat线程不安全的5种解决方案.md
解决这一问题的方法有多种,包括将 SimpleDateFormat 定义为局部变量、使用 synchronized 或 Lock 进行同步控制、利用 ThreadLocal 创建线程私有的实例以及使用 JDK 8 中的 DateTimeFormatter。根据实际应用场景,...
本文将深入探讨`SimpleDateFormat`的线程安全问题及其解决方案。 ### 1. 线程安全问题的原因 `SimpleDateFormat`内部维护了一个`Calendar`对象,用于处理日期和时间的解析与格式化。由于`SimpleDateFormat`不是...
### 关于SimpleDateFormat的非线程安全问题及其解决方案 #### 一、问题介绍 在Java开发过程中,`SimpleDateFormat`是被广泛使用的日期格式化工具类。然而,在多线程环境下,`SimpleDateFormat`存在非线程安全的...
为了解决SimpleDateFormat非线程安全性的问题,可以使用同步代码来避免问题。例如,可以使用synchronized关键字来同步访问SimpleDateFormat实例,也可以使用ThreadLocal变量来保存每个线程的SimpleDateFormat实例。 ...
Java SimpleDateFormat线程安全问题原理详解 Java SimpleDateFormat线程安全问题是Java开发中一个常见的问题。SimpleDateFormat是Java中一个常用的日期时间格式化类,但是它却存在线程安全问题。在多线程环境下,...
为了解决SimpleDateFormat类的线程安全问题,可以使用ThreadLocal类。ThreadLocal类可以使线程绑定到指定的对象,从而避免了线程之间的对象共享问题。在示例代码中,我们使用ThreadLocal类来绑定SimpleDateFormat...
它提供了与`SimpleDateFormat`类似的功能,但避免了线程安全问题。 5. **池化`DateFormat`实例**: 尽管`DateFormat`不是线程安全的,但可以通过池化技术减少创建新实例的开销。创建一个`DateFormat`池,线程在...
Java中的ThreadLocal是解决线程安全问题的一个重要工具,它提供了一种在多线程环境下为每个线程维护独立变量副本的方法,从而避免了共享状态带来的竞态条件和线程安全问题。 线程安全问题通常由全局变量和静态变量...
在Java SE中,传统的日期格式化常常涉及到线程安全问题,特别是当多个线程共享同一实例的`SimpleDateFormat`时。这是因为`SimpleDateFormat`不是线程安全的,它内部使用了可变的状态来处理日期和时间格式化。下面将...
Java在并发环境中使用SimpleDateFormat时,可能会遇到线程安全问题。下面将介绍六种解决方案来解决这个问题。 方法一:使用局部变量 在需要执行格式化的地方都新建SimpleDateFormat实例,使用局部变量来存放...
* 解决线程安全问题可以使用ThreadLocal、Lock、Atomic变量等方式。 五、线程池的重要性 * 线程池可以重用线程,减少线程创建和销毁的开销。 * 线程池可以提高程序的可扩展性和可靠性。 * ThreadPoolExecutor是...
其次,这种同步机制也不能完全解决线程安全问题。 ThreadLocal的解决方案 现在,让我们来看一下使用ThreadLocal的解决方案。在我们的示例代码中,我们使用了ThreadLocal<SimpleDateFormat>来给每个线程单独创建...
##### 实现线程安全的方法: 1. **重入**:允许同一个线程多次进入同一个方法或对象的同步块。 2. **互斥(同步机制)**:通过锁机制来保证在同一时刻只有一个线程可以访问特定的资源。 3. **ThreadLocal**:为每个...
然而,`DateFormat`并不是线程安全的,这意味着在多线程环境中直接使用可能会导致数据不一致或者异常。这篇博客文章《Java DateFormat并发实现》探讨了这个问题以及如何在并发环境下正确地使用`DateFormat`。 `...
4. **线程安全问题**:`SimpleDateFormat`不是线程安全的,如果在多线程环境中未正确同步,可能会导致异常。如果你在并发环境中使用,建议每个线程都有自己独立的`SimpleDateFormat`实例,或使用`java.time` API代替...