`

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

阅读更多

在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是线程不安全的

    在Java编程语言中,`SimpleDateFormat`类是一个广泛使用的日期时间格式化工具,但它的线程安全性是一个常常被开发者忽视的问题。标题指出的"simpleDateFormat是线程不安全的",意味着在多线程环境下,如果多个线程...

    ThreadLocal:如何优雅的解决SimpleDateFormat多线程安全问题

    目录SimpleDateFormat诡异bug复现SimpleDateFormat诡异bug字符串日期转...方法时加锁使用ThreadLocalThreadLocal介绍ThreadLocal使用demoThreadLocal源码探索ThreadLocal注意事项使用ThreadLocal解决SimpleDateFormat...

    高并发之-SimpleDateFormat类的线程安全问题和解决方案.docx

    要解决SimpleDateFormat类的线程安全问题,有多种方法可以选择: 1. 使用ThreadLocal: 可以使用ThreadLocal将SimpleDateFormat对象封装在ThreadLocal中,这样每个线程都有自己的SimpleDateFormat对象,从而避免了...

    SimpleDateFormat线程不安全的5种解决方案.md

    SimpleDateFormat线程不安全的5种解决方案.md

    SimpleDateFormat线程不安全的5种解决方案.docx

    解决这一问题的方法有多种,包括将 SimpleDateFormat 定义为局部变量、使用 synchronized 或 Lock 进行同步控制、利用 ThreadLocal 创建线程私有的实例以及使用 JDK 8 中的 DateTimeFormatter。根据实际应用场景,...

    详解SimpleDateFormat的线程安全问题与解决方案

    本文将深入探讨`SimpleDateFormat`的线程安全问题及其解决方案。 ### 1. 线程安全问题的原因 `SimpleDateFormat`内部维护了一个`Calendar`对象,用于处理日期和时间的解析与格式化。由于`SimpleDateFormat`不是...

    关于SimpleDateFormat的非线程安全问题及其解决方案.docx

    ### 关于SimpleDateFormat的非线程安全问题及其解决方案 #### 一、问题介绍 在Java开发过程中,`SimpleDateFormat`是被广泛使用的日期格式化工具类。然而,在多线程环境下,`SimpleDateFormat`存在非线程安全的...

    深入理解Java:SimpleDateFormat安全的时间格式化

    为了解决SimpleDateFormat非线程安全性的问题,可以使用同步代码来避免问题。例如,可以使用synchronized关键字来同步访问SimpleDateFormat实例,也可以使用ThreadLocal变量来保存每个线程的SimpleDateFormat实例。 ...

    Java SimpleDateFormat线程安全问题原理详解

    Java SimpleDateFormat线程安全问题原理详解 Java SimpleDateFormat线程安全问题是Java开发中一个常见的问题。SimpleDateFormat是Java中一个常用的日期时间格式化类,但是它却存在线程安全问题。在多线程环境下,...

    Java多线程环境下SimpleDateFormat类安全转换

    为了解决SimpleDateFormat类的线程安全问题,可以使用ThreadLocal类。ThreadLocal类可以使线程绑定到指定的对象,从而避免了线程之间的对象共享问题。在示例代码中,我们使用ThreadLocal类来绑定SimpleDateFormat...

    DateFormat多线程问题

    它提供了与`SimpleDateFormat`类似的功能,但避免了线程安全问题。 5. **池化`DateFormat`实例**: 尽管`DateFormat`不是线程安全的,但可以通过池化技术减少创建新实例的开销。创建一个`DateFormat`池,线程在...

    Java ThreadLocal 线程安全问题解决方案

    Java中的ThreadLocal是解决线程安全问题的一个重要工具,它提供了一种在多线程环境下为每个线程维护独立变量副本的方法,从而避免了共享状态带来的竞态条件和线程安全问题。 线程安全问题通常由全局变量和静态变量...

    JavaSE 之 传统日期格式化的线程问题

    在Java SE中,传统的日期格式化常常涉及到线程安全问题,特别是当多个线程共享同一实例的`SimpleDateFormat`时。这是因为`SimpleDateFormat`不是线程安全的,它内部使用了可变的状态来处理日期和时间格式化。下面将...

    Java在并发环境中SimpleDateFormat多种解决方案

    Java在并发环境中使用SimpleDateFormat时,可能会遇到线程安全问题。下面将介绍六种解决方案来解决这个问题。 方法一:使用局部变量 在需要执行格式化的地方都新建SimpleDateFormat实例,使用局部变量来存放...

    高并发编程,高并发编程,高并发编程

    * 解决线程安全问题可以使用ThreadLocal、Lock、Atomic变量等方式。 五、线程池的重要性 * 线程池可以重用线程,减少线程创建和销毁的开销。 * 线程池可以提高程序的可扩展性和可靠性。 * ThreadPoolExecutor是...

    java ThreadLocal使用案例详解

    其次,这种同步机制也不能完全解决线程安全问题。 ThreadLocal的解决方案 现在,让我们来看一下使用ThreadLocal的解决方案。在我们的示例代码中,我们使用了ThreadLocal&lt;SimpleDateFormat&gt;来给每个线程单独创建...

    ThreadLocal

    ##### 实现线程安全的方法: 1. **重入**:允许同一个线程多次进入同一个方法或对象的同步块。 2. **互斥(同步机制)**:通过锁机制来保证在同一时刻只有一个线程可以访问特定的资源。 3. **ThreadLocal**:为每个...

    Java DateFormat并发实现

    然而,`DateFormat`并不是线程安全的,这意味着在多线程环境中直接使用可能会导致数据不一致或者异常。这篇博客文章《Java DateFormat并发实现》探讨了这个问题以及如何在并发环境下正确地使用`DateFormat`。 `...

    java.lang.IllegalArgumentException Cannot format given Object as a Date

    4. **线程安全问题**:`SimpleDateFormat`不是线程安全的,如果在多线程环境中未正确同步,可能会导致异常。如果你在并发环境中使用,建议每个线程都有自己独立的`SimpleDateFormat`实例,或使用`java.time` API代替...

Global site tag (gtag.js) - Google Analytics