在日常开发中,java.text.DateFormat 应该算是使用频率比较高的一个工具类,经常会使用它 将 Date 对象转换成字符串日期,或者将字符串日期转化成 Date 对象。先来看一段眼熟的代码:

public abstract class DateUtils {
 
  private static final DateFormat dateFormatForDay = new SimpleDateFormat("yyyyMMdd");
   
  public static String formatForDay(Date date){
    return dateFormatForDay.format(date);
  }
}

类 DateUtils 的方法 formatForDay() 在多线程的情况下,可能会得不到想要的结果。给一段多线程并发访问 formatForDay() 方法的示例:

public class DateFormatExample {
   
  static final class RunThread implements Runnable{
     
    private int i;
     
    public RunThread(int i) {
      this.i = i;
    }
 
    @Override
    public void run() {
      int count = 0;
      while(true){   
        // create a date
        Calendar c = Calendar.getInstance();
        c.add(Calendar.DAY_OF_MONTH, -i);
        Date d = c.getTime();
        // expect string
        String origianlDate = new SimpleDateFormat("yyyyMMdd").format(d);
        // DateUtils format string
        String afterDate = DateUtils.formatForDay(d);
         
        if(!origianlDate.equals(afterDate)){
          System.out.println("RunThread["+i+"]["+count+"] origianlDate = "+origianlDate
              +", afterDate = "+afterDate);
          System.exit(0);
        }
        count++;
      }
    }
  }
   
  public static void main(String[] args) {
    for(int i=0; i<100; i++){
      new Thread(new RunThread(i)).start();
    }
  }

示例代码,创建了100个 RunThread 线程,每个线程根据 i 的值创建不同的日期,并将日期格式化成字符串,和原日期进行对比,若不相等,则打印退出。某次执行的结果见下:

RunThread[85][1] origianlDate = 20170128, afterDate = 20170122
RunThread[91][5] origianlDate = 20170122, afterDate = 20170222
RunThread[32][7] origianlDate = 20170322, afterDate = 20170114
RunThread[60][3] origianlDate = 20170222, afterDate = 20170202
RunThread[1][0] origianlDate = 20170422, afterDate = 20170401

从结果可以看出,格式化后的日期和传给 format() 的日期不一致。

原因分析

类 DateFormat 有个 Calendar 成员变量:

public abstract class DateFormat extends Format {
 
    protected Calendar calendar;
 
    // ... other code
 
}

调用 format() 方法,会调用实现类 SimpleDateFormat 的  format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) 方法:

public class SimpleDateFormat extends DateFormat {
 
    //... other code
 
    // Called from Format after creating a FieldDelegate
    private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);
 
        //... other code
   }
 
}

每次调用 format() 方法,会将要格式化的日期设置到成员变量 calendar 中,然后再对其进行格式化,此类实现未进行线程同步,是非线程安全的。

当然,调用 parse() 方法,将 字符串转化成日期,也会有同样的非线程安全问题。

解决方案

  1. 每次进行格式化日期调用时,均 new 一个 SimpleDateFormat 对象;缺点是在高并发的情况下,就会频繁创建和销毁对旬,造成开销。
  2. 使用 synchronized 关键字 或 Lock 给静态方法加上同步;缺点是在高并发的情况下,所有的线程在此处会引起资源的竞争。
  3. 使用 ThreadLocal 对象创建静态 DateFormat 。这样在高并必情况下,有多少个线程,就会创建多少个 DateFormat 对象,既不会无限制创建、销毁对象,也不会引起对象的多线程竞争,如下:
public abstract class DateUtils {
 
  private static final ThreadLocal<DateFormat> dateFormatForDay = new ThreadLocal<DateFormat>();
   
  public static String formatForDay(Date date){
    if(dateFormatForDay.get() == null){
      dateFormatForDay.set(new SimpleDateFormat("yyyyMMdd"));
    }
    return dateFormatForDay.get().format(date);
  }
}

java.text.DateFormat 多线程并发问题的更多相关文章

  1. java.text.DateFormat 日期格式化

    一: java.text.DateFormat <%@ page language="java" contentType="text/html; charset=u ...

  2. 常用类一一时间处理相关类一一java.util.Tomezone(java.util.Calendar , java.util.Date , java.text.DateFormat)

    时间处理相关类 时间是一个一维的东东.所以,我们需要一把刻度尺来区表达和度量时间.在计算机世界,我们把1970 年 1 月 1 日 00:00:00定为基准时间,每个度量单位是毫秒(1秒的千分之一). ...

  3. java.text.DateFormat 线程不安全问题

    java.text下的 DateFormat 是线程不安全的: 建议1: 1.使用threadLocal包装DateFormat(太复杂,不推荐) 2.使用org.apache.commons.lan ...

  4. java 多线程:线程安全问题,示例DateFormat多线程执行冲突解决方案ThreadLocal、方法内变量

    SimpleDateFormat多线程中执行报错 java.lang.NumberFormatException: For input string: ""   import ja ...

  5. 【java】Date与String之间的转换及Calendar类:java.text.SimpleDateFormat、public Date parse(String source) throws ParseException和public final String format(Date date)

    package 日期日历类; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util. ...

  6. java.text.ParseException: Unparseable date: "2015-06-09 hh:56:19"

    1.错误描述 [DEBUG:]2015-06-09 16:56:19,520 [-------------------transcation start!--------------] java.te ...

  7. java.text.SimpleDateFormat使用介绍

    java.text.SimpleDateFormat的使用 java.lang.Object   |   +----java.text.Format           |           +-- ...

  8. java.text.ParseException: Failed to parse date ["未知']

    先把"未知"替换为"" 直接new 出来的Gson 对象是无法解析为""的Date属性的,需要通过GsonBuilder来进行创建 Gson ...

  9. java.text.ParseException: Unparseable date: &quot;2015-06-09 hh:56:19&quot;

    1.错误描写叙述 [DEBUG:]2015-06-09 16:56:19,520 [-------------------transcation start!--------------] java. ...

随机推荐

  1. Java获取当前的时间

    Java获取当前的时间 1.利用Java中的Calendar获取当前的时间 具体实现如下: /** * @Title:NowTime.java * @Package:com.you.model * @ ...

  2. (二十三)mongodb中group的问题

    今天的工作中我需要从mongodb数据库中查出一定的数据,并排序后返回给前台,数据库表中包含了ruleID,processingID,userID,updateTime等字段.    同一个ruleI ...

  3. java 后台封装json数据学习总结(一)

    一.数据封装 1. List集合转换成json代码 List list = new ArrayList(); list.add( "first" ); list.add( &quo ...

  4. Linux之shell编程

    一.Bash变量 1) Bash变量与变量分类 1. 定义:变量是计算机内存的单元,其中存放的值可以改变 2. 变量命令规则 #变量名必须以字母或下划线开头,名字中间只能由字母.数字和下划线组成 #变 ...

  5. C#图解教程 第十九章 LINQ

    LINQ 什么是LINQLINQ提供程序 匿名类型 方法语法和查询语法查询变量查询表达式的结构 from子句join子句什么是联结查询主体中的from-let-where片段 from子句let子句w ...

  6. 网页加载进度的实现--JavaScript基础

    总结了一些网页加载进度的实现方式…… 1.定时器实现加载进度 <!DOCTYPE html><html lang="en"><head> < ...

  7. 彻底禁用Chrome的“请停用以开发者模式运行的扩展程序”提示

    前言 作为一个前端程序员,难免会有一些专属自己的小扩展,没必要每一个都发到Chrome应用商店去,虽然可以勾选"开发者模式"来运行本地插件,但是每次启动都会有一个烦人的" ...

  8. 【BZOJ1899】午餐(动态规划)

    [BZOJ1899]午餐(动态规划) 题面 BZOJ 题解 我太弱了 这种\(dp\)完全做不动.. 首先,感性理解一些 如果所有人都要早点走, 那么,吃饭时间长的就先吃 吃饭时间短的就晚点吃 所以, ...

  9. HNOI2017 单旋

    题目描述 网址:https://www.luogu.org/problemnew/show/3721 大意: 有一颗单旋Splay(Spaly),以key值为优先度,总共有5个操作. [1] 插入一个 ...

  10. Bzoj4869: [Shoi2017]相逢是问候

    题面 传送门 Sol 摆定理 \[ a^b\equiv \begin{cases} a^{b\%\phi(p)}~~~~~~~~~~~gcd(a,p)=1\\ a^b~~~~~~~~~~~~~~~~~ ...