关于 SimpleDateFormat 的非线程安全问题及其解决方案
一直以来都是直接用SimpleDateFormat开发的,没想着考虑线程安全的问题,特记录下来(摘抄的):
1、问题:
先来看一段可能引起错误的代码:
package test.date; import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale; public class ProveNotSafe {
static SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
static String testdata[] = { "01-Jan-1999", "14-Feb-2001", "31-Dec-2007" }; public static void main(String[] args) {
Runnable r[] = new Runnable[testdata.length];
for (int i = 0; i < r.length; i++) {
final int i2 = i;
r[i] = new Runnable() {
public void run() {
try {
for (int j = 0; j < 1000; j++) {
String str = testdata[i2];
String str2 = null;
/* synchronized(df) */{
Date d = df.parse(str);
str2 = df.format(d);
System.out.println("i: " + i2 + "\tj: " + j + "\tThreadID: "
+ Thread.currentThread().getId() + "\tThreadName: "
+ Thread.currentThread().getName() + "\t" + str + "\t" + str2);
}
if (!str.equals(str2)) {
throw new RuntimeException("date conversion failed after " + j
+ " iterations. Expected " + str + " but got " + str2);
}
}
} catch (ParseException e) {
throw new RuntimeException("parse failed");
}
}
};
new Thread(r[i]).start();
}
}
}
结果(随机失败):
i: 2 j: 0 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 1 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 2 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 3 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 4 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 5 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 6 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 7 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 8 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 9 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 10 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 11 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 12 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 13 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 14 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 15 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 16 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 17 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 11-Jan-1999
i: 0 j: 0 ThreadID: 8 ThreadName: Thread-0 01-Jan-1999 11-Jan-1999
Exception in thread "Thread-2" i: 1 j: 0 ThreadID: 9 ThreadName: Thread-1 14-Feb-2001 11-Jan-2001
Exception in thread "Thread-0" java.lang.RuntimeException: date conversion failed after 0 iterations. Expected 01-Jan-1999 but got 11-Jan-1999
at test.date.ProveNotSafe$1.run(ProveNotSafe.java:30)
at java.lang.Thread.run(Thread.java:619)
Exception in thread "Thread-1" java.lang.RuntimeException: date conversion failed after 0 iterations. Expected 14-Feb-2001 but got 11-Jan-2001
at test.date.ProveNotSafe$1.run(ProveNotSafe.java:30)
at java.lang.Thread.run(Thread.java:619)
java.lang.RuntimeException: date conversion failed after 17 iterations. Expected 31-Dec-2007 but got 11-Jan-1999
at test.date.ProveNotSafe$1.run(ProveNotSafe.java:30)
at java.lang.Thread.run(Thread.java:619)
恩,原因你是知道了,这是由于 SimpleDateFormat 的非线程安全问题引起的,
我们现在简化下问题,错误的代码应该是这样的:
import java.text.SimpleDateFormat;
import java.util.Date; public class DateUtil { SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); public String formatDate(Date input) {
return sdf.format(input);
} }

2、解决方案
(1)使用局部变量:
import java.text.SimpleDateFormat;
import java.util.Date; public class DateUtil { private static final String SIMPLE_FORMAT = "dd/MM/yyyy"; public String formatDate(Date input) { if(input == null){
return null;
} SimpleDateFormat sdf = new SimpleDateFormat(SIMPLE_FORMAT);//local variable
return sdf.format(input);
}
}

恩,这是线程安全的了,不是吗?
(2)使用 ThreadLocal
这里每个线程将有它自己的 SimpleDateFormat 副本。
import java.text.SimpleDateFormat;
import java.util.Date; public class DateUtil { // anonymous inner class. Each thread will have its own copy of the SimpleDateFormat
private final static ThreadLocal<simpledateformat> tl = new ThreadLocal<simpledateformat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("dd/MM/yyyy");
}
};http://my.oschina.net/huangyong/blog/159725 public String formatDate(Date input) {
if (input == null) {
return null;
} return tl.get().format(input);
}
}
PS:顺便聊聊这个 ThreadLocal:
ThreadLocal 按我的理解是一个Map容器,视作其key是当前线程,value就是我们想保证数据安全一致的某对象。从它的功能上来说,应该叫做 ThreadLocalVariable(线程局部变量)更合适些。
具体的含义与作用请参考如下两篇文摘:ThreadLocal 那点事儿
http://my.oschina.net/huangyong/blog/159489
http://my.oschina.net/huangyong/blog/159725
(3)同步代码块 synchronized(code)
或者使用装饰器设计模式包装下 SimpleDateFormat ,使之变得线程安全。
(4)使用第三方的日期处理函数:
比如 JODA 来避免这些问题,你也可以使用 commons-lang 包中的 FastDateFormat 工具类。
(5)最后的提问:
上面几种方案中,有最佳方案吗?如果不是最佳,各有什么优劣?
PS:
顺便吐槽下 java 的日期处理类真TMD是个渣。。。有坑不说,关键是难用。。。这点 shell 的 date 命令应该是用户体验做的最好的了~ ![]()
REF:
http://java-success.blogspot.com/2012/07/java-coding-question-and-answer-on.html
http://www.codefutures.com/weblog/andygrove/2007/10/simpledateformat-and-thread-safety.html
关于变量的线程安全问题,请参考:
java 线程安全问题之静态变量、实例变量、局部变量
http://my.oschina.net/leejun2005/blog/130043
关于 SimpleDateFormat 的非线程安全问题及其解决方案的更多相关文章
- SimpleDateFormat类的线程安全问题和解决方案
摘要:我们就一起看下在高并发下SimpleDateFormat类为何会出现安全问题,以及如何解决SimpleDateFormat类的安全问题. 本文分享自华为云社区<SimpleDateForm ...
- 044 SimpleDateFormat的线程安全问题与解决方案
这个问题,以前好像写过,不过现在这篇文章,有一个重现的过程,还是值得读一读的. URL:SimpleDateFormat的线程安全问题与解决方案
- SimpleDateFormat使用和线程安全问题
SimpleDateFormat 是一个以国别敏感的方式格式化和分析数据的具体类. 它允许格式化 (date -> text).语法分析 (text -> date)和标准化. Simpl ...
- SimpleDateFormat的线程安全问题与解决方案
SimpleDateFormat 是 Java 中一个常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题. 因为 DateFormat 和 Simple ...
- 使用原子类或synchronized(没用Lock)解决阐述多线程所遇到线程安全问题和解决方案
例子题目: 创建10个线程,每个线程执行10000次加1,输出总和 正常结果100000 但是如果出现线程不安全会低于100000 import java.util.concurrent.Count ...
- SimpleDateFormat非线程安全
文章列表 1)SimpleDateFormat的线程安全问题与解决方案 2)深入理解Java:SimpleDateFormat安全的时间格式化
- SimpleDateFormat的线程安全问题
做项目的时候查询的日期总是不对,花了很长时间才找到异常的根源,原来SimpleDateFormat是非线程安全的,当我把这个类放到多线程的环境下转换日期就会出现莫名奇妙的结果,这种异常找出来可真不容易 ...
- stl空间配置器线程安全问题补充
摘要 在上一篇博客<STL空间配置器那点事>简单介绍了空间配置器的基本实现 两级空间配置器处理,一级相关细节问题,同时简单描述了STL各组件之间的关系以及设计到的设计模式等. 在最后,又关 ...
- java基础25 线程的常用方法、线程安全问题、死锁现象
一.线程的常用方法 1.Thread(String name):初始化线程的名字2. setName(String name):设置线程的名字3. getName():返回线程的名字4. sleep( ...
随机推荐
- JPA 对象关系映射之关联关系映射策略
关联关系映射 关联关系映射,是映射关系中比较复杂的一种映射关系,总的说来有一对一.一对多和多对多几种关系.细分起来他们又有单向和双向之分.下面我们逐一介绍一下. 回页首 单向 OneToOne 单向一 ...
- node.js---sails项目开发(1)
1.安装Node.js和npm---这里就做介绍啦! 2.需要全局下安装Sails sudo npm install sails -g 3. 在本地创建一个文件夹 mkdir ~/lsg/sails ...
- git发布代码到github过程和常见错误
在对git有了基本了解之后,并且常常看到很多人在github上发布代码和开源项目时,就会想如何也把自己的代码发布到github上,并能够不断的跟踪版本变化. 现在就有几个想要做的事. 一.如何把自己已 ...
- C# 创建单例你会几种方式?
关于为什么需要创建单例?这里不过多介绍,具体百度知. 关于C# 创建单例步骤或条件吧 1.声明静态变量:2.私有构造函数(无法实例化)3.静态创建实例的方法:至于我这里的Singleton是seal ...
- 有趣的Linux命令,让终端炫起来
10条真心有趣的Linux命令 动画演示10个有趣但毫无用处的Linux命令 11个无用而有趣的Linux终端彩蛋 lolcat :一个在 Linux 终端中输出彩虹特效的命令行工具
- java二叉排序树
二叉排序树又称二叉查找树.它或者是一颗空树,或者是具有如下性质的二叉树: 1.如果左子树不空,那么左子树上的所有节点均小于它的根节点的值: 2.如果右子树不空,那么右子树上的所有节点均大于它的根节点的 ...
- HDOJ 1501 Zipper 【DP】【DFS+剪枝】
HDOJ 1501 Zipper [DP][DFS+剪枝] Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Ja ...
- ng-click得到当前元素,
直接上代码: <!DOCTYPE html> <html> <head> <title></title> <script src=&q ...
- PM,RD,FE,UE,UI,QA,OP,DBA,BRD,MRD,PRD,FSD全称解析
PM 项目经理( Project Manager ) 从职业角度,是指企业建立以项目经理责任制为核心,对项目实行质量.安全.进度.成本管理的责任保证体系和全面提高项目管理水平设立的重要管理 ...
- Shell脚本监控专线Network并SendEmail报警
Much effort, much prosperity. 配置服务器监控专线网络 项目介绍: 配置一台服务器监控专线网络的情况,(由于公司业务的原因需要用到专线进行与客户对接)实时监控专线的状态,如 ...