当前位置:首页 > 开发 > 编程语言 > 多线程 > 正文

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

发表于: 2013-05-22   作者:bijian1013   来源:转载   浏览:
摘要: 在Java项目中,我们通常会自己写一个DateUtil类,处理日期和字符串的转换,如下所示: public class DateUtil01 { private SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public void format(Date d

在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
...
...
...

 

      从如上运行结果来看,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.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

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

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

编辑推荐
SimpleDateFormat 的线程安全问题 SimpleDateFormat 是一个以国别敏感的方式格式化和分析数据的具体
转自线程为什么是不安全的 一直以来只是知道HashMap是线程不安全的,但是到底HashMap为什么线程不安
servlet 线程不安全,想必大家都知道了,本来想在网上找个例子试验下,结果没找到....还是自己写一
Date类    在 JDK 1.1 之前,类 Date 有两个其他的函数。它允许把日期解释为年、月、日、小时、分
解决 本页不但包含安全的内容,也包含不安全的内容是否显示不安全的内容 工具--->Internet选项--
在窗体程序中我们常把费时操作另开新线程,但是我们要知道新线程的运行状态,又要对UI线程进行操作
首先在第一个线程组里讲你需要保存的值放入到jmeter的某个属性中,属性名名字自己定义,如上图的tok
//第一种方法使用jsonp的方式 <script type="text/javascript" src="http://www.youxiaju.com/js
2013年12月18日 今天晚上喝了杯咖啡,趁着兴奋的劲,一不小心找出了困扰好长时间的Bug 阶段1、两个
一、第一个说法 可重入函数 在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这
版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号