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

news/2024/7/6 4:38:31 标签: java, thread, SimpleDateFormat, 线程安全

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

java">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.日期工具处理类的接口

java">package com.bijian.study.date;

import java.util.Date;

public interface DateUtilInterface {

	public void format(Date date);
	public void parse(String str);
}

 

2.日期工具实现类

java">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.调用日期工具的线程类

java">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.测试主方法

java">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方法是否有线程安全问题

java">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).使用同步

java">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实例

java">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变量的拷贝或者叫做副本,代码如下:

java">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方法,主要作用是创建时初始化实例。

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

java">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


http://www.niftyadmin.cn/n/1319067.html

相关文章

最详细的axios+vuex请求封装以及使用实例

目录一、Vux相关介绍1.Vuex基本概念二、Axios相关介绍1.axios基本概念三、AxiosVuex获取后端数据1.实例演示使用VuexAxios获取后端数据2.Vuex中对应的login.js文件请求后端四、异步操Vuex1.实例一、Vux相关介绍 1.Vuex基本概念 Vuex 是一个专为 Vue.js 应用程序开发的状态管理…

Python抓取中文网页

早就有想法把博客每天的访问流量记下来&#xff0c;刚好现在申请了GAE的应用&#xff0c;又开始学Python&#xff0c;正好拿这个练手。打算先利用Python把访问记录保存在本地&#xff0c;熟悉之后可以部署到GAE&#xff0c;利用GAE提供的cron就可以每天更近访问流量了。OK&…

$(document).ready()方法 VS window.onload VS $(window).load()及load事件详解

今天发现一个问题&#xff0c;修改页面时&#xff0c;无法对一个按钮进行条件的显示或隐藏&#xff0c;同事用setTimeout解决了。但我老觉得setTimeout不到万不得已的时候才能用。 经分析&#xff0c;这个要显示或隐藏的按钮是include进来的&#xff0c;于是用window.onload解决…

对话框中设置静态文本框字体及颜色

1、添加对话框类声明中字体和颜色变量 public:CFont m_myFont; // 字体对象COLORREF m_myColor; // 颜色对象 2、在对话框初始化函数OnInitDialog()中对字体和颜色进行初始化 // TODO: 在此添加额外的初始化代码m_myFont.CreatePointFont(150, _T("华文彩云"));m_my…

Angular基础知识系列学习(一)--Angular的介绍以及基本环境搭建

Angular从入门到放弃秘籍 第一篇Angular的介绍以及基本环境搭建 第二篇Angular模板语法、插值语法、事件绑定 第三篇Angular内置基本指令介绍 第四篇Angular组件的创建、组件声明周期钩子函数 第五篇Angular父子组件传值&#xff0c;父传子&#xff0c;子传父&#xff0c;…

JUC 源码学习 CountDownLatch 源码学习

JUC 源码学习 CountDownLatch 源码学习 1.CountDownLatch 构造方法 ountDownLatch countDownLatch new CountDownLatch(n);public CountDownLatch(int count) {//如果构造参数少于0&#xff0c;抛出异常if (count < 0) throw new IllegalArgumentException("count …

JQuery对象的val()方法执行结果分析

JavaScript中&#xff0c;如果id对应的标签不存在&#xff08;同理JAVA中&#xff0c;如果对象不存在&#xff09;&#xff0c;则调用它的方法会报错或抛异常。在实际开发中&#xff0c;发现JQuery在id对应的标签不存在时&#xff0c;调其val()方法不会报错&#xff0c;结果是u…

Vue 项目 echarts中国地图、柱状图、折线图、大数据监控平台实现

Vue 项目echarts中国地图、柱状图、折线图、大数据监控平台实现 点击下载源代码