转载来自:http://blog.csdn.net/zxh87/article/details/19414885

1.结论

DateFormat和SimpleDateFormat都不是线程安全的。在多线程环境中调用format()和parse()应处理线程安全的问题。

2.错误示例

(1)错误示例1

每次处理一个时间信息,都新建一个SimpleDateFormat实例,然后再丢弃。造成内存的浪费。

 package com.peidasoft.dateformat;

 import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date; public class DateUtil { public static String formatDate(Date date)throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
} public static Date parse(String strDate) throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(strDate);
}
}

(2)错误示例2

为了防止频繁创建,使用静态的SimpleDateFormat实例,所有关于时间的处理都使用这个静态实例。

缺点是:在多线程环境中会有问题,比如转换的时间不对,线程被挂死或者报奇怪的错误等等。都是因为SimpleDateFormat线程不安全造成的。

 package com.peidasoft.dateformat;

 import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date; public class DateUtil {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDate(Date date)throws ParseException{
return sdf.format(date);
} public static Date parse(String strDate) throws ParseException{
return sdf.parse(strDate);
}
}

3.源码

JDK文档中对于DateFormat的说明:

SimpleDateFormat中的日期格式不是同步的。推荐(建议)为每个线程创建独立的格式实例。如果多个线程同时访问一个格式,则它必须保持外部同步。

 Synchronization
  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.

SimpleDateFormat继承了DateFormat。在DateFormat中定义了一个Calendar类的对象calendar。因为Calendar累的概念复杂,牵扯到时区与本地化等等,所以Jdk的实现中使用了成员变量来传递参数

format方法如下:

 private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
calendar.setTime(date);
boolean useDateFormatSymbols = useDateFormatSymbols(); for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
  count = compiledPattern[i++] << 16;
  count |= compiledPattern[i++];
} switch (tag) {
  case TAG_QUOTE_ASCII_CHAR:
    toAppendTo.append((char)count);
  break;   case TAG_QUOTE_CHARS:
    toAppendTo.append(compiledPattern, i, count);
    i += count;
  break;   default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
  break;
}
}
return toAppendTo;
}

calendar.setTime(date)这条语句改变了calendar,稍后,calendar还会用到(在subFormat方法里),而这就是引发问题的根源。

在一个多线程环境下,有两个线程持有了同一个SimpleDateFormat的实例,分别调用format方法:

  • 线程1调用format方法,改变了calendar这个字段。
  • 中断。
  • 线程2开始执行,它也改变了calendar。
  • 又中断。
  • 线程1回来了,此时,calendar已然不是它所设的值,再往下执行就可能会出现错误。

如果多个线程同时争抢calendar对象,则会出现各种问题,时间不对,线程挂死等等。

这个问题背后隐藏着一个更为重要的问题--无状态。

无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以它是有状态的。

这也同时提醒我们在开发和设计系统的时候注意下一下三点:

  • 自己写公用类的时候,要对多线程调用情况下的后果在注释里进行明确说明。
  • 对线程环境下,对每一个共享的可变变量都要注意其线程安全性。
  • 我们的类和方法在做设计的时候,要尽量设计成无状态的。

4.解决办法

(1)如果不特别考虑性能,可以采用错误示例1中的用法,每用到一个SimpleDateFormat就新建一个

(2)如果考虑性能,想使用错误示例2中的形式,就需要采取额外的同步措施

 public class DateSyncUtil {

     private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

     public static String formatDate(Date date)throws ParseException{
synchronized(sdf){
return sdf.format(date);
}
} public static Date parse(String strDate) throws ParseException{
synchronized(sdf){
return sdf.parse(strDate);
}
}
}

(3)如果要更加考虑性能,可以使用ThreadLocal

使用ThreadLocal, 也是将共享变量变为独享,线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。如果对性能要求比较高的情况下,一般推荐使用这种方法。

写法一:

 package com.peidasoft.dateformat;

 import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date; public class ConcurrentDateUtil { private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
}; public static Date parse(String dateStr) throws ParseException {
return threadLocal.get().parse(dateStr);
} public static String format(Date date) {
return threadLocal.get().format(date);
}
}

写法二:

 public class ThreadLocalDateUtil {
private static final String date_format = "yyyy-MM-dd HH:mm:ss";
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
public static DateFormat getDateFormat(){
DateFormat df = threadLocal.get();
if(df==null){
df = new SimpleDateFormat(date_format);
threadLocal.set(df);
}
return df;
} public static String formatDate(Date date) throws ParseException {
return getDateFormat().format(date);
} public static Date parse(String strDate) throws ParseException {
return getDateFormat().parse(strDate);
}
}

5.测试和结论

做一个简单的压力测试,方法一最慢,方法三最快。

但是就算是最慢的方法一性能也不差,一般系统方法一和方法二就可以满足,所以说在这个点很难成为系统的瓶颈所在。从简单的角度来说,建议使用方法一或者方法二,如果在必要的时候,追求那么一点性能提升的话,可以考虑用方法三,用ThreadLocal做缓存。

ps:Joda-Time类库对时间处理方式比较完美,建议使用。(待学习)

2017.12.11SimpleDateFormat的线程安全性讨论的更多相关文章

  1. [No000016E]Spring 中获取 request 的几种方法,及其线程安全性分析

    前言 本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性. 原创不易,如果觉得文章对你有帮助,欢迎点赞.评论.文章有疏漏之处,欢迎批评指正. 欢迎 ...

  2. Spring中获取request的几种方法,及其线程安全性分析(山东数漫江湖)

    前言 本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性. 原创不易,如果觉得文章对你有帮助,欢迎点赞.评论.文章有疏漏之处,欢迎批评指正. 欢迎 ...

  3. 我是怎样测试Java类的线程安全性的

    线程安全性是Java等语言/平台中类的一个重要标准,在Java中,我们经常在线程之间共享对象.由于缺乏线程安全性而导致的问题很难调试,因为它们是偶发的,而且几乎不可能有目的地重现.如何测试对象以确保它 ...

  4. Java并发编程学习笔记(一)——线程安全性

    主要概念:线程安全性.原子性.原子变量.原子操作.竟态条件.复合操作.加锁机制.重入.活跃性与性能. 1.当多个线程访问某个状态变量并且其中有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变 ...

  5. Spring中获取request的几种方法,及其线程安全性分析

    前言 本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性. 原创不易,如果觉得文章对你有帮助,欢迎点赞.评论.文章有疏漏之处,欢迎批评指正. 欢迎 ...

  6. 【Java并发.2】线程安全性

    要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享(Shared)和可变的(Mutable)状态的访问. “共享”意味着变量可以由多个线程同时访问,而“可变”则意味着变量的值在其生 ...

  7. 【转】SpringMVC,获取request的几种方法,及线程安全性

    作者丨编程迷思 https://www.cnblogs.com/kismetv/p/8757260.html 概述 在使用Spring MVC开发Web系统时,经常需要在处理请求时使用request对 ...

  8. Java并发(理论知识)—— 线程安全性

    1.什么是线程安全性                                                                                      当多个线 ...

  9. Java并发编程(五):Java线程安全性中的对象发布和逸出

    发布(Publish)和逸出(Escape)这两个概念倒是第一次听说,不过它在实际当中却十分常见,这和Java并发编程的线程安全性就很大的关系. 什么是发布?简单来说就是提供一个对象的引用给作用域之外 ...

随机推荐

  1. 弹出框美化 alert样式美化

    引用style.css和ui.js就可以直接用以下接口调用!(文末附完整代码) alert_带标题: mizhu.alert('alert_带标题', '这是alert效果'); alert_带图标: ...

  2. 使用Gradle管理第三方依赖

    http://blog.bsdn.org/2012/01/02/%E4%BD%BF%E7%94%A8gradle%E7%AE%A1%E7%90%86%E7%AC%AC%E4%B8%89%E6%96%B ...

  3. python中文转换url编码

    今天要处理百度贴吧的东西.想要做一个关键词的list,每次需要时,直接添加 到list里面就可以了.但是添加到list里面是中文的情况(比如‘丽江’),url的地址编码却是’%E4%B8%BD%E6% ...

  4. python spyder 今天突然打不开了【已解决】

    python spyder 我是设置开机启动的,先出现dos窗口,然后是蜘蛛网,后面就什么都没有了.然后百度了半天,在csdn看到一篇文章,试了一下,内牛满面! 方法:C:\Documents and ...

  5. Codeforces 713A. Sonya and Queries

    题目链接:http://codeforces.com/problemset/problem/713/A 题意: Sonya 有一个可放置重复元素的集合 multiset, 初始状态为空, 现给予三种类 ...

  6. 洛谷P3539 [POI2012] ROZ-Fibonacci Representation

    题目传送门 转载自:five20,转载请注明出处 本来看到这题,蒟蒻是真心没有把握的,还是five20大佬巨orz 首先由于斐波拉契数的前两项是1,1 ,所以易得对于任何整数必能写成多个斐波拉契数加减 ...

  7. 取得指定Schema下的表

    MYSQL中取得指定Schema下所有表定义的SQL语句如下(假设Schema名为demoschema): SHOWTABLES FROM demoschema MSSQLServer中的系统表sys ...

  8. 初见Python<4>:字典

    序列是python中的一种数据结构,映射是另一种.映射(mapping)通过名字来引用值.python内建的唯一一种映射结构是字典.字典中的值没有特殊的顺序,但都存储在一个特定的键中.键可以是数字.字 ...

  9. [JSOI2017]原力(分块+map(hash))

    题目描述 一个原力网络可以看成是一个可能存在重边但没有自环的无向图.每条边有一种属性和一个权值.属性可能是R.G.B三种当中的一种,代表这条边上 原力的类型.权值是一个正整数,代表这条边上的原力强度. ...

  10. bzoj 3540: [Usaco2014 Open]Fair Photography

    3540: [Usaco2014 Open]Fair Photography Description FJ's N cows (2 <= N <= 100,000) are standin ...