SimpleDateFormat是线程不安全的,不能多个线程公用。而FastDateFormat和Joda-Time都是线程安全的,可以放心使用。

SimpleDateFormat是JDK提供的,不需要依赖第三方jar包,而其他两种都得依赖第三方jar包。

FastDateFormat是apache的commons-lang3包提供的

Joda-Time需要依赖以下maven的配置(现在最新版本就是2.10.1)

<!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>

一、SimpleDateFormat,线程不安全

SimpleDateFormatFastDateFormat主要都是对时间的格式化

SimpleDateFormat在对时间进行格式化的方法format中,会先对calendar对象进行setTime的赋值,若是有多个线程同时操作一个SimpleDateFormat实例的话,就会对calendar的赋值进行覆盖,进而产生问题。

在format方法里,有这样一段代码:

  1. private StringBuffer format(Date date, StringBuffer toAppendTo,
  2. FieldDelegate delegate) {
  3. // Convert input date to time field list
  4. calendar.setTime(date);
  5.  
  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已然不是它所设的值,而是走上了线程2设计的道路。如果多个线程同时争抢calendar对象,则会出现各种问题,时间不对,线程挂死等等。
  分析一下format的实现,我们不难发现,用到成员变量calendar,唯一的好处,就是在调用subFormat时,少了一个参数,却带来了这许多的问题。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解
  这个问题背后隐藏着一个更为重要的问题--无状态:无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的。

有三种方法可以解决这个问题:

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

说明:在需要用到SimpleDateFormat 的地方新建一个实例,不管什么时候,将有线程安全问题的对象由共享变为局部私有都能避免多线程问题,不过也加重了创建对象的负担。在一般情况下,这样其实对性能影响比不是很明显的。

2、使用同步的方式,在调用方法的时候加上synchronized,这样可以让线程调用方法时,进行加锁,也就是会造成线程间的互斥,对性能影响比较大。

  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 DateSyncUtil {
  8.  
  9. private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  10.  
  11. public static String formatDate(Date date)throws ParseException{
  12. synchronized(sdf){
  13. return sdf.format(date);
  14. }
  15. }
  16.  
  17. public static Date parse(String strDate) throws ParseException{
  18. synchronized(sdf){
  19. return sdf.parse(strDate);
  20. }
  21. }
  22. }

说明:当线程较多时,当一个线程调用该方法时,其他想要调用此方法的线程就要block,多线程并发量大的时候会对性能有一定的影响。

3、使用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. 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 ThreadLocalDateUtil {
  9. private static final String date_format = "yyyy-MM-dd HH:mm:ss";
  10. private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
  11. public static DateFormat getDateFormat()
  12. {
  13. DateFormat df = threadLocal.get();
  14. if(df==null){
  15. df = new SimpleDateFormat(date_format);
  16. threadLocal.set(df);
  17. }
  18. return df;
  19. }
  20.  
  21. public static String formatDate(Date date) throws ParseException {
  22. return getDateFormat().format(date);
  23. }
  24.  
  25. public static Date parse(String strDate) throws ParseException {
  26. return getDateFormat().parse(strDate);
  27. }
  28. }

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

二、FastDateFormat,线程安全的,可以直接使用,不必考虑多线程的情况

  1. FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");
  2. System.out.println(format.format(new Date()));
  3.  
  4. // 可以使用DateFormatUtils类来操作,方法里面也是使用的FastDateFormat类来做的
  5. System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));

三、Joda-Time,线程安全

Joda-Time与以上两种有所区别,不仅仅可以对时间进行格式化输出,而且可以生成瞬时时间值,并与Calendar、Date等对象相互转化,极大的方便了程序的兼容性。

Joda-Time的类具有不可变性,因此他们的实例是无法修改的,就跟String的对象一样。

这种不可变性提现在所有API方法中,这些方法返回的都是新的类实例,与原来实例不同。

以下是Joda-Time的一些使用方法

  1. // 得到当前时间
  2. Date currentDate = new Date();
  3. DateTime dateTime = new DateTime(); // DateTime.now()
  4.  
  5. System.out.println(currentDate.getTime());
  6. System.out.println(dateTime.getMillis());
  7.  
  8. // 指定某一个时间,如2016-08-29 15:57:02
  9. Date oneDate = new Date(1472457422728L);
  10. DateTime oneDateTime = new DateTime(1472457422728L);
  11. DateTime oneDateTime1 = new DateTime(2016, 8, 29, 15, 57, 2, 728);
  12.  
  13. System.out.println(oneDate.toString());
  14. System.out.println(oneDateTime.toString()); // datetime默认的输出格式为yyyy-MM-ddTHH:mm:ss.SSS
  15. System.out.println(oneDateTime1.toString("MM/dd/yyyy hh:mm:ss.SSSa")); // 直接就可以输出规定的格式
  16.  
  17. // DateTime和Date之间的转换
  18. Date convertDate = new Date();
  19. DateTime dt1 = new DateTime(convertDate);
  20. System.out.println(dt1.toString());
  21.  
  22. Date d1 = dt1.toDate();
  23. System.out.println(d1.toString());
  24.  
  25. // DateTime和Calendar之间的转换
  26. Calendar c1 = Calendar.getInstance();
  27. DateTime dt2 = new DateTime(c1);
  28. System.out.println(dt2.toString());
  29.  
  30. Calendar c2 = dt2.toCalendar(null); // 默认时区Asia/Shanghai
  31. System.out.println(c2.getTimeZone());
  32.  
  33. // 时间格式化
  34. DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
  35. DateTime dt3 = DateTime.parse("2016-08-29 13:32:33", formatter);
  36. System.out.println(dt3.toString());
  37. // 若是不指定格式,会采用默认的格式,yyyy-MM-ddTHH:mm:ss.SSS,若被解析字符串只到年月日,后面的时分秒会全部默认为0
  38. DateTime dt4 = DateTime.parse("2016-08-29T");
  39. System.out.println(dt4.toString());
  40. // 输出locale 输出2016年08月29日 16:43:14 星期一
  41. System.out.println(new DateTime().toString("yyyy年MM月dd日 HH:mm:ss EE", Locale.CHINESE));
  42.  
  43. // 计算两个日期间隔的天数
  44. LocalDate start = new DateTime().toLocalDate();
  45. LocalDate end = new LocalDate(2016, 8, 25);
  46. System.out.println(Days.daysBetween(start ,end).getDays()); // 这里要求start必须早于end,否则计算出来的是个负数
  47. // 相同的还有间隔年数、月数、小时数、分钟数、秒数等计算
  48. // 类如Years、Hours等
  49.  
  50. // 对日期的加减操作
  51. DateTime dt5 = new DateTime();
  52. dt5 = dt5.plusYears(1) // 增加年
  53. .plusMonths(1) // 增加月
  54. .plusDays(1) // 增加日
  55. .minusHours(1) // 减小时
  56. .minusMinutes(1) // 减分钟
  57. .minusSeconds(1); // 减秒数
  58. System.out.println(dt5.toString());
  59.  
  60. // 判断是否闰月
  61. DateTime dt6 = new DateTime();
  62. DateTime.Property month = dt6.monthOfYear();
  63. System.out.println(month.isLeap());

原文链接:https://blog.csdn.net/zxh87/article/details/19414885https://jjhpeopl.iteye.com/blog/2321528

日期格式化:SimpleDateFormat【线程不安全】、FastDateFormat和Joda-Time【后两个都是线程安全】的更多相关文章

  1. java 日期格式化-- SimpleDateFormat 的使用。字符串转日期,日期转字符串

    日期和时间格式由 日期和时间模式字符串 指定.在 日期和时间模式字符串 中,未加引号的字母 'A' 到 'Z' 和 'a' 到 'z' 被解释为模式字母,用来表示日期或时间字符串元素.文本可以使用单引 ...

  2. Java中日期格式化SimpleDateFormat类包含时区的处理方法

    1.前言 需要把格式为“2017-02-23T08:04:02+01:00”转化成”23-02-2017-T15:04:02“格式(中国时区为+08:00所以是15点),通过网上查找答案,发现没有我需 ...

  3. 七:日期类Date、日期格式化SimpleDateFormat、日历Calendar

    日期的格式转换:

  4. JavaScript 日期格式化 简单有用

    JavaScript 日期格式化 简单有用 代码例如以下,引入jquery后直接后增加下面代码刷新可測试 Date.prototype.Format = function (fmt) { //auth ...

  5. 【Java】学习路径49-练习:使用两个不同的线程类实现买票系统

    练习:使用两个不同的线程类实现买票系统 请创建两个不同的线程类.一个测试类以及一个票的管理类. 其中票的管理类用于储存票的数量.两个线程类看作不同的买票方式. 步骤: 1.创建所需的类 App售票线程 ...

  6. 日期时间格式化 SimpleDateFormat与DateTimeFormatter

    原文:https://www.jianshu.com/p/b212afa16f1f 1.SimpleDateFormat为什么不是线程安全的? 如果我们把SimpleDateFormat定义成stat ...

  7. Java SE基础部分——常用类库之SimpleDateFormat(日期格式化)

    取得当前日期,并按照不同日期格式化输入.代码如下: // 20160618 SimpleDateFomat类的使用 日期格式化 练习 package MyPackage; //自己定义的包 impor ...

  8. 真没想到,Springboot能这样做全局日期格式化,有点香!

    最近面了一些公司,有一些 Java方面的架构.面试资料,有需要的小伙伴可以在公众号[程序员内点事]里,无套路自行领取 说在前边 最近部门几位同事受了一些委屈相继离职,共事三年临别之际颇有不舍,待一切手 ...

  9. JDK8 日期格式化

    SpringBoot 是为了简化 Spring 应用的创建.运行.调试.部署等一系列问题而诞生的产物,自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖 ...

随机推荐

  1. Mac新手入门:mac操作技巧

    面对全新的mac电脑,你是不是一脸的迷茫,一些原来windows上的基本操作在mac上都不知道从何入手了,下面小编就为大家整理了一些基本的操作.相信一定会方便你的学习和工作的. 如何压缩与解压缩 在M ...

  2. Codeforces 512B: Fox And Jumping

    题目链接 题意说的是,有n种卡片,使用第i种卡片可以使当前自己在数轴上的位置移动 l[i],要获得使用第i种卡片的代价是 c[i],求能使自己移动到数轴上任意位置的最小代价,如果不可能则输出-1 当前 ...

  3. 英语单词character

    来源——tr帮助说明 TR() User Commands TR() NAME tr - translate or delete characters SYNOPSIS tr [OPTION]... ...

  4. Page.after

    解释: Page.after可以增加Page级的切面,触发的时机是在所拦截的对应生命周期方法执行之后,也可以拦截所有页面上发生的事件(对于要拦截的事件,在swan文件上必须显示绑定了相应事件). 方法 ...

  5. pl/sql中return和exit区别

    经测试: 1.exit只能用于循环中,并且退出循环往下执行: 2.return可用于循环或非循环,并且退出整个程序模块不往下执行. declare i number :=1; j number :=1 ...

  6. Python 装饰器之 functools.wraps

    在看 Bottle 代码中看见 functools.wraps 这种用法. def make_default_app_wrapper(name): """ Return ...

  7. 【进阶技术】一篇文章搞掂:Spring Cloud Stream

    本文总结自官方文档http://cloud.spring.io/spring-cloud-static/spring-cloud-stream/2.1.0.RC3/single/spring-clou ...

  8. 牛客:t次询问,每次给你一个数n,求在[1,n]内约数个数最多的数的约数个数(数论+贪心)

    https://ac.nowcoder.com/acm/contest/907/B t次询问,每次给你一个数n,求在[1,n]内约数个数最多的数的约数个数 分析: 根据约数和定理:对于一个大于1正整数 ...

  9. 「PHP开发APP接口实战009」日常安全防范之防SQL入和XSS攻击

    防SQL注入和XSS攻击通用过滤 首先在 /app/library/ 目录下创建 Security.php 文件并添加以下代码: <?php /** * * 防SQL注入和XSS攻击通用过滤 * ...

  10. Oracle Data Guard Protection Modes

    Maximum Availability This protection mode provides the highest level of data protection that is poss ...