博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SimpleDateFormat线程不安全及解决的方法
阅读量:6700 次
发布时间:2019-06-25

本文共 4913 字,大约阅读时间需要 16 分钟。

一. 为什么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
local = new ThreadLocal
(); 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();		}	}}

转载地址:http://dlgoo.baihongyu.com/

你可能感兴趣的文章
iOS开发~UI布局(二)storyboard中autolayout和size class的使用详解
查看>>
Redis配置文件参数说明
查看>>
C#文件拖到TextBox中获取文件显示文件路径
查看>>
TSQL 根据表名生成UPDATE SELECT INSERT
查看>>
程序员的思维修炼》读书笔记
查看>>
Java第五次作业--面向对象高级特性(抽象类和接口)
查看>>
Linux进程间通信(四) - 共享内存
查看>>
python-访问者模式
查看>>
事件处理
查看>>
安卓自定义View进阶-分类与流程
查看>>
iOS开发多线程篇—线程安全
查看>>
android 学习随笔十六(广播 )
查看>>
WorldWind Java 版学习:1、启动过程
查看>>
cep
查看>>
获取 docker 容器(container)的 ip 地址
查看>>
postgresql安装配置
查看>>
softlayer virtual machine vhd磁盘镜像导入shell脚本
查看>>
python cookbook 笔记三
查看>>
Command 传参的几种方式
查看>>
android 弹起键盘把ui顶上去的解决办法
查看>>