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-08-29 08:33:08

SimpleDateFormat非线程安全及解决办法的相关文章

tomcat关闭后线程依然运行解决办法

tomcat关闭后线程依然运行解决办法,设置线程为守护线程 守护线程与非守护线程 最近在看多线程的Timer章节,发现运用到了守护线程,感觉Java的基础知识还是需要补充. Java分为两种线程:用户线程和守护线程 所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分.因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程.反过来说,只要任何非守护线程还在运行,程序就不会终止. 守

线程间操作无效: 从不是创建控件“”的线程访问它 解决办法(转)

线程间操作无效: 从不是创建控件“”的线程访问它 解决办法 http://blog.sina.com.cn/s/blog_568e66230101der7.html 利用FileSystemWatcher设计一个文件监控系统时,如果一个文件被修改或者新建,则文件修改事件会被多次触发而产生多条信息.为了将一个文件被修改一次而产生的多条信息归结为一条,在设计中新开了一个线程,在指定时间内(如2秒内)这个文件的修改被认为是一次修改,从而只产生一条信息. 这个工作完成后,又出现了另外一个问题:因为需要在

《java多线程编程核心技术》----simpleDateFormat非线程安全

类simpleDateFormat主要负责日期的转换和格式化,但在多线程的环境中,使用此内容容易造成数据转换以及处理的不准确, 因为simpleDateFormat类并不是线程安全的. public class MyThread extends Thread { private SimpleDateFormat sdf; private String dateString; public MyThread(SimpleDateFormat sdf, String dateString) { su

SimpleDateFormat非线程安全

以前没有注意过的问题呀! 留着 自己好好看看. 出自 :http://www.cnblogs.com/zemliu/p/3290585.html 1. 原因 SimpleDateFormat(下面简称sdf)类内部有一个Calendar对象引用,它用来储存和这个sdf相关的日期信息,例如sdf.parse(dateStr), sdf.format(date) 诸如此类的方法参数传入的日期相关String, Date等等, 都是交友Calendar引用来储存的.这样就会导致一个问题,如果你的sdf

ESXi主机出现“主机上的系统日志存储在非持久存储器中”解决办法

如果esxi主机出现"未在主机XXX.XXX.XXX.XXX上配置系统日志记录"或者"主机上的系统日志存储在非持久存储器中"的报错,可尝试进行如下操作. 选中出错的esxi主机,在管理--设置--高级系统设置中,找到Syslog.global.logDir这一行参数 点编辑.输入log文件所存放的路径: 以上图为例路径是[vsanDatastore]/scratch/log,你得保证该datastore下有这个目录哦,如果没有,你可以用datastore浏览器手动创

SequoiaDB报告创建线程失败的解决办法

1.问题背景 对于分布式数据库和分布式环境,高并发和高性能压力的情况下,出现线程创建失败等等问题也是十分常见的,这时候就十分考虑数据库管理员的经验,需要能快速的定位到问题和瓶颈所在,快速解决.本文也是作为一个最佳实践,告诉大家如何在高并发情况下定位问题,排除问题,解决瓶颈. 2.问题定位 SequoiaDB在集群环境中的 -10 错误码,在认真查阅节点的 diaglog 日志后,发现是操作系统 create thread 失败的问题. 如我们的测试环境下,SequoiaDB节点的 diaglog

面试官系统精讲Java源码及大厂真题系列之Java线程安全的解决办法

1. 背景 1.1 static修饰类变量.方法.方法块.  public + static = 该变量任何类都可以直接访问,而且无需初始化类,直接使用 类名.static 变量 1.2 多个线程同时对共享变量进行读写时,很有可能会出现并发问题.(存在共享数据时才需要考虑线程安全) 1.3 public static List<String> list = new ArrayList(); 这个 list 如果同时被多个线程访问的话,就有线程安全的问题. 2. 解决方法 2.1特定策略解决线程

Spring @ResponseBody 返回乱码 的优雅解决办法

版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 返回的结果中,中文全部被问号(?)代替的解决办法: *-servlet.xml的部分配置如下: [html] view plain copy <bean id="utf8Charset" class="java.nio.charset.Charset" factory-method="forName"> <constructor-arg value=&quo

非直连IBGP邻居路由更新的解决办法(一)-------路由反射器

如上图所示,R1/R2/R3在同一个IBGP内,这种情况下,路由更新,从IBGP邻居学习的路由不会被传递给其他的IBGP邻居.这样的解决办法有三种: 1.IBGP完全互连 2.路由反射器 (Route Reflector) 3.联盟 案例一.非直连邻居通过路由反射器(直连邻居省略) R1配置信息: interface Loopback10 ip address 10.1.1.1 255.255.255.0 ! interface Loopback100 ip address 100.1.1.1