转载来自: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. es6中对象的一些操坐

    变量的赋值 key值得构建 对象的严格检测 对象的合并 1.变量的赋值: let name='宋宇',age='17岁': let obj={name,age} //快速的将变量引入到对象中去. 2. ...

  2. CentOS7安装配置WPS

    1.下载 地址:http://wps-community.org/downloads 2.安装 rpm -ivh wps-office-10.1.0.5707-1.a21.x86_64.rpm 3.运 ...

  3. Loadrunner脚本读取 XMl 文件

    Loadrunner脚本读取 XMl 文件 性能测试工程师要懂代码么?答案是必须的,好多测试员认为在 loadrunner 中编写脚本很难很牛 X . 好多人认为 loadrunner 只支持 C 语 ...

  4. win 10 下面安装 mysql-8.0.12-winx64 的过程

    win 10 下面安装 mysql-8.0.12-winx64 的过程 1.官网下载 mysql 2.解压到你要安装的目录 3.在mysql目录D:\Programming\mysql-8.0.12- ...

  5. 使用for循环打印9×9乘法表

    请使用for循环,倒序打印9×9乘法表. 打印结果如下图所示: 使用for循环打印9×9乘法表 #include <stdio.h> int main() { int i, j, resu ...

  6. ceph 安装记录

    ceph 安装 http://docs.ceph.com/docs/master/start/quick-ceph-deploy/ 1. 系统要求 centos 7 systemctl disable ...

  7. 转:Google Project Zero挖洞经验整理

    https://www.sec-un.org/google-project-zero%E6%8C%96%E6%B4%9E%E7%BB%8F%E9%AA%8C%E6%95%B4%E7%90%86/ 1. ...

  8. 删除元素(LintCode)

    删除元素 给定一个数组和一个值,在原地删除与值相同的数字,返回新数组的长度. 元素的顺序可以改变,并且对新的数组不会有影响. 样例 给出一个数组 [0,4,4,0,0,2,4,4],和值 4 返回 4 ...

  9. 洛谷——P1724 东风谷早苗

    P1724 东风谷早苗 题目描述 在幻想乡,东风谷早苗是以高达控闻名的高中生宅巫女.某一天,早苗终于入手了最新款的钢达姆模型.作为最新的钢达姆,当然有了与以往不同的功能了,那就是它能够自动行走,厉害吧 ...

  10. matlab学习之求解函数的根和极小值

    只是用来求解函数的部分一个根,具体算法没查询,只是调用自带的函数 代码如下 % 求函数零点和极小值 xiszero=func(0) x=-1:0.1:1.5; y=func(x); plot(x,y) ...