SimpleDateFormat线程不安全及解决的方法

一. 为什么SimpleDateFormat不是线程安全的?

Java源代码例如以下:

/**
* Date formats are not synchronized.
* It is recommended to create separate format instances for each thread.
* If multiple threads access a format concurrently, it must be synchronized
* externally.
*/
public class SimpleDateFormat extends DateFormat {

	public Date parse(String text, ParsePosition pos){
		calendar.clear(); // Clears all the time fields
		// other logic ...
		Date parsedDate = calendar.getTime();
	}
}

abstract class DateFormat{
	// other logic ...
	protected Calendar calendar;
	public Date parse(String source) throws ParseException{
        ParsePosition pos = new ParsePosition(0);
        Date result = parse(source, pos);
        if (pos.index == 0)
            throw new ParseException("Unparseable date: \"" + source + "\"" ,
                pos.errorIndex);
        return result;
    }
}

假设我们把SimpleDateFormat定义成static成员变量,那么多个thread之间会共享这个sdf对象, 所以Calendar对象也会共享。

假定线程A和线程B都进入了parse(text, pos) 方法。 线程B运行到calendar.clear()后,线程A运行到calendar.getTime(), 那么就会有问题。

二. 问题重现:

public class DateFormatTest {
	private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
	private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" };

	public static void main(String[] args) {
		for (int i = 0; i < date.length; i++) {
			final int temp = i;
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						while (true) {
							String str1 = date[temp];
							String str2 = sdf.format(sdf.parse(str1));
							System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2);
							if(!str1.equals(str2)){
								throw new RuntimeException(Thread.currentThread().getName()
										+ ", Expected " + str1 + " but got " + str2);
							}
						}
					} catch (Exception e) {
						throw new RuntimeException("parse failed", e);
					}
				}
			}).start();
		}
	}
}

创建三个进程, 使用静态成员变量SimpleDateFormat的parse和format方法。然后比較经过这两个方法折腾后的值是否相等:

程序假设出现下面错误,说明传入的日期就串掉了,即SimpleDateFormat不是线程安全的:

Exception in thread "Thread-0" java.lang.RuntimeException: parse failed
	at cn.test.DateFormatTest$1.run(DateFormatTest.java:27)
	at java.lang.Thread.run(Thread.java:662)
Caused by: java.lang.RuntimeException: Thread-0, Expected 01-Jan-1999 but got 01-Jan-2000
	at cn.test.DateFormatTest$1.run(DateFormatTest.java:22)
	... 1 more

三. 解决方式:

1. 解决方式a:

将SimpleDateFormat定义成局部变量:

SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
String str1 = "01-Jan-2010";
String str2 = sdf.format(sdf.parse(str1));

缺点:每调用一次方法就会创建一个SimpleDateFormat对象,方法结束又要作为垃圾回收。

2. 解决方式b:

加一把线程同步锁:synchronized(lock)

public class SyncDateFormatTest {
	private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
	private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" };

	public static void main(String[] args) {
		for (int i = 0; i < date.length; i++) {
			final int temp = i;
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						while (true) {
							synchronized (sdf) {
								String str1 = date[temp];
								Date date = sdf.parse(str1);
								String str2 = sdf.format(date);
								System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2);
								if(!str1.equals(str2)){
									throw new RuntimeException(Thread.currentThread().getName()
											+ ", Expected " + str1 + " but got " + str2);
								}
							}
						}
					} catch (Exception e) {
						throw new RuntimeException("parse failed", e);
					}
				}
			}).start();
		}
	}
}

缺点:性能较差,每次都要等待锁释放后其它线程才干进入

3. 解决方式c: (推荐)

使用ThreadLocal: 每一个线程都将拥有自己的SimpleDateFormat对象副本。

写一个工具类:

public class DateUtil {
	private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>();

	public static Date parse(String str) throws Exception {
		SimpleDateFormat sdf = local.get();
		if (sdf == null) {
			sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
			local.set(sdf);
		}
		return sdf.parse(str);
	}

	public static String format(Date date) throws Exception {
		SimpleDateFormat sdf = local.get();
		if (sdf == null) {
			sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
			local.set(sdf);
		}
		return sdf.format(date);
	}
}

測试代码:

public class ThreadLocalDateFormatTest {
	private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" };

	public static void main(String[] args) {
		for (int i = 0; i < date.length; i++) {
			final int temp = i;
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						while (true) {
							String str1 = date[temp];
							Date date = DateUtil.parse(str1);
							String str2 = DateUtil.format(date);
							System.out.println(str1 + "," + str2);
							if(!str1.equals(str2)){
								throw new RuntimeException(Thread.currentThread().getName()
										+ ", Expected " + str1 + " but got " + str2);
							}
						}
					} catch (Exception e) {
						throw new RuntimeException("parse failed", e);
					}
				}
			}).start();
		}
	}
}
时间: 2024-09-29 09:31:13

SimpleDateFormat线程不安全及解决的方法的相关文章

queue非线程安全及多线程解决的方法

stl的queue是非线程安全的 比方以下的应用场景: 子线程对queue队列做push操作,同一时候主线程对queue运行pop操作,则可能会发生异常. 解决的方法: 方案1: 自己写一个循环队列,则不存在同一时候push与pop的问题. 方案2: 加一个全局相互排斥锁.核心代码例如以下: #define LOCK_G_MSGQUEUE() while(g_free_lock == true) { usleep(10*1000); } g_free_lock = true; #define U

Application.Exit()结束程序,但线程还在的解决方法。

Application.Exit()结束程序,但线程还在的解决方法. 出现此情况大多原因是使用了多线程编程,或者你所调用的dll使用了多线程.我们知道,一般情况下的线程执行完指定的任务之后是会关闭了的,但是如果对于一些循环类线程,或者忘记关掉的线程时,这个时候就需要我们手动将之强制关闭.用以下三个中的一个即可尝试强制关闭线程. 复制内容到剪贴板 代码: Application.ExitThread();//退出当前线程上的消息循环,并关闭该线程上的所有窗口.    复制内容到剪贴板 代码: Sy

Atitit.swt&#160;线程调用ui控件的方法

Atitit.swt 线程调用ui控件的方法 1 SwingUtilities.invokeLater1 2 display.asyncExec方法1 3  display.timerExec(500,timer);2 4 .但有时候并不一定要程序执行时就要定时检测,有时需要外部事情激发这就出现了第2种解决方案,写一个内置类,可以放在事件监听的方法中,然后激发:2 5 参考3 1   SwingUtilities.invokeLater SwingUtilities.invokeLater(ne

【转载】Android中UI线程与后台线程交互设计的5种方法

原帖地址:http://www.cr173.com/html/19165_1.html 在android的设计思想中,为了确保用户顺滑的操作体验.一些耗时的任务不能够在UI线程中运行,像访问网络就属于这类任务.因此我们必须要重新开启一个后台线程运行这些任务.然而,往往这些任务最终又会直接或者间接的需要访问和控制UI控件.例如访问网络获取数据,然后需要将这些数据处理显示出来.就出现了上面所说的情况.原本这是在正常不过的现象了,但是android规定除了UI线程外,其他线程都不可以对那些UI控件访问

.NET一个线程更新另一个线程的UI(两种实现方法及若干简化)

Winform中的控件是绑定到特定的线程的(一般是主线程),这意味着从另一个线程更新主线程的控件不能直接调用该控件的成员. 控件绑定到特定的线程这个概念如下: 为了从另一个线程更新主线程的Windows Form控件,可用的方法有: 首先用一个简单的程序来示例,这个程序的功能是:在Winfrom窗体上,通过多线程用label显示时间.给出下面的两种实现方式 1.结合使用特定控件的如下成员 InvokeRequired属性:返回一个bool值,指示调用者在不同的线程上调用控件时是否必须使用Invo

jQuery同步Ajax带来的UI线程阻塞问题及解决办法

原文:jQuery同步Ajax带来的UI线程阻塞问题及解决办法 俗话说不作死就不会死,今天作死了一回,写了一个比较二逼的函数,遇到了同步Ajax引起的UI线程阻塞问题,在此记录一下. 事情起因是这样的,因为页面上有多个相似的异步请求动作,本着提高代码可重用性的原则,我封装了一个名为getData的函数,它接收不同参数,只负责获取数据,然后把数据return.基本的逻辑剥离出来是这样的: function getData1(){ var result; $.ajax({ url : 'p.php'

java线程共享受限资源 解决资源竞争 thinking in java4 21.3

java线程共享受限资源 解决资源竞争  详细介绍请参阅:thinking in java4 21.3 thinking in java 4免费下载:http://download.csdn.net/detail/liangrui1988/7580155 package org.rui.thread.res; /** * 不正确的访问 资源 * @author lenovo * */ public abstract class IntGenerator { private volatile bo

Android中UI线程与后台线程交互设计的5种方法

我想关于这个话题已经有很多前辈讨论过了.今天算是一次学习总结吧. 在android的设计思想中,为了确保用户顺滑的操作体验.一 些耗时的任务不能够在UI线程中运行,像访问网络就属于这类任务.因此我们必须要重新开启一个后台线程运行这些任务.然而,往往这些任务最终又会直接或者 间接的需要访问和控制UI控件.例如访问网络获取数据,然后需要将这些数据处理显示出来.就出现了上面所说的情况.原本这是在正常不过的现象了,但是 android规定除了UI线程外,其他线程都不可以对那些UI控件访问和操控.为了解决

C# 多线程学习(五)线程同步和冲突解决

from:https://blog.csdn.net/codedoctor/article/details/74358257 首先先说一个线程不同步的例子吧,以下为售票员的模拟售票,多个售票员出售100张门票,代码如下: using System; using System.Text; using System.Collections.Generic; using System.Threading; namespace threadTest { class Program { class Thr