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

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

hot3.png

之前有同事好几次都掉这个坑里去了,刚好今天有看到有篇帖子提了下,索性就整理下吧~

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-2007i: 2	j: 1	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 2	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 3	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 4	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 5	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 6	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 7	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 8	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 9	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 10	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 11	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 12	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 13	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 14	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 15	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 16	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	31-Dec-2007i: 2	j: 17	ThreadID: 10	ThreadName: Thread-2	31-Dec-2007	11-Jan-1999i: 0	j: 0	ThreadID: 8	ThreadName: Thread-0	01-Jan-1999	11-Jan-1999Exception in thread "Thread-2" i: 1	j: 0	ThreadID: 9	ThreadName: Thread-1	14-Feb-2001	11-Jan-2001Exception 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
tl = new ThreadLocal
() { protected SimpleDateFormat initialValue() { return new SimpleDateFormat("dd/MM/yyyy"); } } public String formatDate(Date input) { if (input == null) { return null; } return tl.get().format(input); }}

PS:顺便聊聊这个 ThreadLocal:

ThreadLocal 按我的理解是一个Map容器,视作其key是当前线程,value就是我们想保证数据安全一致的某对象。从它的功能上来说,应该叫做 ThreadLocalVariable(线程局部变量)更合适些。

具体的含义与作用请参考如下两篇文摘:ThreadLocal 那点事儿

(3)同步代码块 synchronized(code)

或者使用装饰器设计模式包装下 SimpleDateFormat ,使之变得线程安全。

(4)使用第三方的日期处理函数:

比如 JODA 来避免这些问题,你也可以使用 commons-lang 包中的 FastDateFormat 工具类。

(5)最后的提问:

上面几种方案中,有最佳方案吗?如果不是最佳,各有什么优劣?

PS:

顺便吐槽下 java 的日期处理类真TMD是个渣。。。有坑不说,关键是难用。。。这点 shell 的 date 命令应该是用户体验做的最好的了~ 

REF:

[1] Java 编程之美:并发编程高级篇之一

[2] java 线程安全问题之静态变量、实例变量、局部变量

转载于:https://my.oschina.net/leejun2005/blog/152253

你可能感兴趣的文章
PHP经典算法题
查看>>
LeetCode 404 Sum of Left Leaves
查看>>
醋泡大蒜有什么功效
查看>>
hdu 5115(2014北京—dp)
查看>>
数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)...
查看>>
PHP读取日志里数据方法理解
查看>>
第五十七篇、AVAssetReader和AVAssetWrite 对视频进行编码
查看>>
Vivado增量式编译
查看>>
一个很好的幻灯片效果的jquery插件--kinMaxShow
查看>>
微信支付签名配置正确,但返回-1,调不出支付界面(有的手机能调起,有的不能)...
查看>>
第二周例行报告
查看>>
Spring学习(16)--- 基于Java类的配置Bean 之 基于泛型的自动装配(spring4新增)...
查看>>
实验八 sqlite数据库操作
查看>>
四种简单的排序算法(转)
查看>>
Quartz2D之着色器使用初步
查看>>
多线程条件
查看>>
Git [remote rejected] xxxx->xxxx <no such ref>修复了推送分支的错误
查看>>
Porter/Duff,图片加遮罩setColorFilter
查看>>
黄聪:VMware安装Ubuntu10.10【图解】转
查看>>
Centos 6.x 升级openssh版本
查看>>