2017.12.11SimpleDateFormat的线程安全性讨论

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

1.结论

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

2.错误示例

(1)错误示例1

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

 1 package com.peidasoft.dateformat;
 2
 3 import java.text.ParseException;
 4 import java.text.SimpleDateFormat;
 5 import java.util.Date;
 6
 7 public class DateUtil {
 8
 9     public static  String formatDate(Date date)throws ParseException{
10         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
11         return sdf.format(date);
12     }
13
14     public static Date parse(String strDate) throws ParseException{
15          SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
16         return sdf.parse(strDate);
17     }
18 }

(2)错误示例2

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

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

 1 package com.peidasoft.dateformat;
 2
 3 import java.text.ParseException;
 4 import java.text.SimpleDateFormat;
 5 import java.util.Date;
 6
 7 public class DateUtil {
 8     private static final  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 9
10     public static  String formatDate(Date date)throws ParseException{
11         return sdf.format(date);
12     }
13
14     public static Date parse(String strDate) throws ParseException{
16         return sdf.parse(strDate);
17     }
18 }

3.源码

JDK文档中对于DateFormat的说明:

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

1 Synchronization:
2   Date formats are not synchronized.
3   It is recommended to create separate format instances for each thread.
4   If multiple threads access a format concurrently, it must be synchronized externally.

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

format方法如下:

 1 private StringBuffer format(Date date, StringBuffer toAppendTo,
 2                                 FieldDelegate delegate) {
 4    calendar.setTime(date);
 6    boolean useDateFormatSymbols = useDateFormatSymbols();
 7
 8    for (int i = 0; i < compiledPattern.length; ) {
 9         int tag = compiledPattern[i] >>> 8;
10         int count = compiledPattern[i++] & 0xff;
11         if (count == 255) {
12           count = compiledPattern[i++] << 16;
13           count |= compiledPattern[i++];
14         }
15
16         switch (tag) {
17           case TAG_QUOTE_ASCII_CHAR:
18             toAppendTo.append((char)count);
19           break;
20
21           case TAG_QUOTE_CHARS:
22             toAppendTo.append(compiledPattern, i, count);
23             i += count;
24           break;
25
26           default:
27                 subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
28           break;
29         }
30     }
31         return toAppendTo;
32     }

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中的形式,就需要采取额外的同步措施

 1 public class DateSyncUtil {
 2
 3     private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 4
 5     public static String formatDate(Date date)throws ParseException{
 6         synchronized(sdf){
 7             return sdf.format(date);
 8         }
 9     }
10
11     public static Date parse(String strDate) throws ParseException{
12         synchronized(sdf){
13             return sdf.parse(strDate);
14         }
15     }
16 }

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

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

写法一:

 1 package com.peidasoft.dateformat;
 2
 3 import java.text.DateFormat;
 4 import java.text.ParseException;
 5 import java.text.SimpleDateFormat;
 6 import java.util.Date;
 7
 8 public class ConcurrentDateUtil {
 9
10     private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
11         @Override
12         protected DateFormat initialValue() {
13             return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
14         }
15     };
16
17     public static Date parse(String dateStr) throws ParseException {
18         return threadLocal.get().parse(dateStr);
19     }
20
21     public static String format(Date date) {
22         return threadLocal.get().format(date);
23     }
24 }

写法二:

 1 public class ThreadLocalDateUtil {
 2     private static final String date_format = "yyyy-MM-dd HH:mm:ss";
 3     private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
 4     public static DateFormat getDateFormat(){
 6         DateFormat df = threadLocal.get();
 7         if(df==null){
 8             df = new SimpleDateFormat(date_format);
 9             threadLocal.set(df);
10         }
11         return df;
12     }
13
14     public static String formatDate(Date date) throws ParseException {
15         return getDateFormat().format(date);
16     }
17
18     public static Date parse(String strDate) throws ParseException {
19         return getDateFormat().parse(strDate);
20     }
21 }

5.测试和结论

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

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

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

时间: 2024-10-01 04:48:02

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

单例模式的写法和线程安全性的讨论

//饿汉模式:单例模式,就是无论用不用,什么时候用,在类加载的时候就实例化一个这个类的对象 //然后等到使用的时候,就是使用同一个实例对象 //好处:在多线程的环境下使用这种方法,可以避免多线程带来的冲突.与之相对应的是,懒汉模式(按需实例化) class Singleton1{ private Singleton1(){} private static Singleton1 s1 = new Singleton1(); public static Singleton1 getSingleton

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

线程安全性是Java等语言/平台中类的一个重要标准,在Java中,我们经常在线程之间共享对象.由于缺乏线程安全性而导致的问题很难调试,因为它们是偶发的,而且几乎不可能有目的地重现.如何测试对象以确保它们是线程安全的? 假如有一个内存书架 package com.mzc.common.thread; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * <p class="detail"

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

1.当多个线程访问某个状态变量并且其中有一个献策灰姑娘执行写入操作时,必须采用同步机制来协同这些线程对变量的访问.Java中的主要同步机制是关键字synchronized,他提供了一种独占的加锁方式. 2.在任何情况下,只有当类中仅包含自己的状态时,线程安全类才是有意义的. 3.当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些献策灰姑娘讲如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 4.无状态对象一定是线程安全的

Java线程安全性中的对象发布和逸出

发布(Publish)和逸出(Escape)这两个概念倒是第一次听说,不过它在实际当中却十分常见,这和Java并发编程的线程安全性就很大的关系. 什么是发布?简单来说就是提供一个对象的引用给作用域之外的代码.比如return一个对象,或者作为参数传递到其他类的方法中. 什么是逸出?如果一个类还没有构造结束就已经提供给了外部代码一个对象引用即发布了该对象,此时叫做对象逸出,对象的逸出会破坏线程的安全性. 概念我们知道了,可我们要关注什么地方呢?我们要关注的时候就是逸出问题,在不该发布该对象的地方就

线程安全性

多线程程序中,如果控制不好,经常会出现各种的问题,有时候问题在调试或者测试的时候就会暴露出来,最要命的是程序部署一段时间之后才出现各种怪异现象,感到头疼?需要补充Java并发的知识了.该读书笔记系列以<Java并发编程实战>为基础,同时会参考网络上一些其他的资料,和大家一起学习Java并发编程的各个方面.这方面也是笔者比较薄弱的地方,理解不对的地方请留言或者邮件指出,同时也欢迎讨论.邮箱:[email protected] 线程安全性这章,将从如下几个方面入手,描述探讨: 1.什么是对象的状态

ConcurrentHashMap和 CopyOnWriteArrayList提供线程安全性和可伸缩性 以及 同步的集合类 Hashtable 和 Vector Collections.synchronizedMap 和 Collections.synchronizedList 区别缺点

ConcurrentHashMap和 CopyOnWriteArrayList提供线程安全性和可伸缩性 DougLea的 util.concurrent 包除了包含许多其他有用的并发构造块之外,还包含了一些主要集合类型 List 和 Map 的高性能的.线程安全的实现.在本月的 Java理论与实践中,BrianGoetz向您展示了用 ConcurrentHashMap 替换 Hashtable 或 synchronizedMap ,将有多少并发程序获益. 在Java类库中出现的第一个关联的集合类

浅析HashMap与ConcurrentHashMap的线程安全性

本文要解决的问题: 最近无意中发现有很多对Map尤其是HashMap的线程安全性的话题讨论,在我的理解中,对HashMap的理解中也就知道它是线程不安全的,以及HashMap的底层算法采用了链地址法来解决哈希冲突的知识,但是对其线程安全性的认知有限,故写这篇博客的目的就是让和我一样对这块内容不熟悉的小伙伴有一个对HashMap更深的认知. 哈希表 在数据结构中有一种称为哈希表的数据结构,它实际上是数组的推广.如果有一个数组,要最有效的查找某个元素的位置,如果存储空间足够大,那么可以对每个元素和内

Spring中如何获取request的方法汇总及其线程安全性分析

前言 本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性.下面话不多说了,来一起看看详细的介绍吧. 概述 在使用Spring MVC开发Web系统时,经常需要在处理请求时使用request对象,比如获取客户端ip地址.请求的url.header中的属性(如cookie.授权信息).body中的数据等.由于在Spring MVC中,处理请求的Controller.Service等对象都是单例的,因此获取request对象时最需要注意的问题,便是

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

概述在使用Spring MVC开发Web系统时,经常需要在处理请求时使用request对象,比如获取客户端ip地址.请求的url.header中的属性(如cookie.授权信息).body中的数据等.由于在Spring MVC中,处理请求的Controller.Service等对象都是单例的,因此获取request对象时最需要注意的问题,便是request对象是否是线程安全的:当有大量并发请求时,能否保证不同请求/线程中使用不同的request对象.这里还有一个问题需要注意:前面所说的"在处理请