转载来自: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. EOJ Monthly 2018.8 D. Delivery Service-树上差分(边权/边覆盖)(边权转点权)(模板题)

    D. Delivery Service 单测试点时限: 2.5 秒 内存限制: 512 MB EOJ Delivery Service Company handles a massive amount ...

  2. SQL练习总结

    [SQL语句练习] 1. 表1: Person +-------------+---------+ | 列名 | 类型 | +-------------+---------+ | PersonId | ...

  3. java 反射 获取Class对象的三种方式

    String str="hello world"; //方式一 :通过对象的getClass()方法 Class<?> clazz1 =str.getClass(); ...

  4. Visual Studio Xamarin中找不到iOS模拟器

    Visual Studio Xamarin中找不到iOS模拟器 Visual Studio可以正常连接Mac系统,但是在测试时候,提示以下错误信息:Failed to start iOS Simula ...

  5. response (响应对象)

    一.response响应过程 在去发送一个请求时, 会找到tomcat引擎 引擎会找到对应的web应用,并且会创建request对象和response对象 找到应用后, 会执行应用的web.xml再去 ...

  6. 【计算几何】【圆反演】计蒜客17314 2017 ACM-ICPC 亚洲区(南宁赛区)网络赛 G. Finding the Radius for an Inserted Circle

    题意:给你三个半径相同的圆,它们切在一起,然后让你往缝里一个一个地塞圆,问你塞到第k个的半径是多少. 就把上面那两个圆的切点当成反演中心,然后会反演成这个样子,两个平行直线和一个圆. 然后就是往那个圆 ...

  7. openfire安装完毕后无法登录控制台(忘记密码)的解决方法

    openfire登录管理控制提示: Login failed:make sure your username and password are correct and that you’re an a ...

  8. 最近公共祖先 LCA 倍增法

    [简介] 解决LCA问题的倍增法是一种基于倍增思想的在线算法. [原理] 原理和同样是使用倍增思想的RMQ-ST 算法类似,比较简单,想清楚后很容易实现. 对于每个节点u , ancestors[u] ...

  9. STL迭代器及迭代器失效问题

    迭代器失效: 典型的迭代器失效. 首先对于vector而言,添加和删除操作可能使容器的部分或者全部迭代器失效.那为什么迭代器会失效呢?vector元素在内存中是顺序存储,试想:如果当前容器中已经存在了 ...

  10. <摘录>ldconfig和ldd用法

    ldconfig和ldd用法 关键字: ldconfig ldd /etc/ld.so.conf LD_LIBRARY_PATH一.ldconfig ldconfig --helpUsage: ldc ...