转载来自: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. AC日记——小A的糖果 洛谷七月月赛

    小A的糖果 思路: for循环贪心: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 100005 #defi ...

  2. 深刻剖析VuGen脚本录制原理

    扩展:深刻剖析VuGen脚本录制原理 LR中的VuGen组件,主要扮演一个proxy server的角色,在录制脚本时,记录下用户和服务器交互,然后自动生成脚本语言.在接下来的重放,或者大批量地加压时 ...

  3. UnicodeEncodeError: 'gbk' codec can't encode character ...

    使用Python写文件的时候,或者将网络数据流写入到本地文件的时候,大部分情况下会遇到:UnicodeEncodeError: 'gbk' codec can't encode character ' ...

  4. [thinkphp] 无限极分类

    <?php /* * 无限极分类 类 */ header("Content-Type: text/html; charset=UTF-8"); Class Category ...

  5. 服务器Tomcat WAS JBoss

    做任何web项目,都离不开服务器,有钱的公司用WebSphere.WebLogic,没钱公司用nginx+tomcat,不要小瞧nginx+tomcat麻雀虽小,五脏俱全. 服务器的知识,在笔试.面试 ...

  6. AC自动机及KMP练习

    好久都没敲过KMP和AC自动机了.以前只会敲个kuangbin牌板子套题.现在重新写了自己的板子加深了印象.并且刷了一些题来增加自己的理解. KMP网上教程很多,但我的建议还是先看AC自动机(Trie ...

  7. python基础之类与对象,继承与派生

    类与对象 对象的本质也就是一个名称空间而已,用于存放自己独有的属性,而类中存放的是对象共有的属性. __init__会在调用类时自动触发 调用类时发生两件事: 1.创建一个空对象stu1 2.自动触发 ...

  8. [LOJ500]ZQC的拼图

    题目大意: 给你一个m*m的格子,让你往里面放给定的直角三角形,直角顶点必须放在右上角且不能翻转,但是可以把所有给定的三角形放大一个整数倍k,问至少放大几倍能使格子的左下角和右上角连起来?(可以超出边 ...

  9. 求数组的平均值 Exercise07_08

    import java.util.Scanner; /** * @author 冰樱梦 * 时间:2018年下半年 * 题目:求数组的平均值 * */ public class Exercise07_ ...

  10. Java编程思想学习(四)----第四章:控制执行流程

    在Java中,涉及的关键字包括if-else.while.do-while.for.return.break 以及选择语句switch.然而,Java并不支持goto语句(该语句引起许多反对意见,但它 ...