在日常开发中,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. 代码管理必备-----git使用上传码云

    作为一个程序员,你要学会代码的管理,这是一个最基本的修养,就像是一个剑客的剑谱,代码管理,目前流行的是svn和git,但是很不好的是git如果没有插件的话,很多人都不会用git bash 来实现自己的 ...

  2. input【type="checkbox"】标签与字体对齐

    今天分享一个比较实用的技巧,在实际项目中我们会经常遇到表单的input标签多选和单选的问题,但是往往由于标签自身的样式和我们项目的风格很不搭调,就不能实现了,今天就来告诉大家怎么去实现吧. 第一种:利 ...

  3. 关于省市联动的bug

    一,问题描述 1.1,原来的思路 1.1.1,初始化加载省份 $.ajax({ 'type' : 'POST', 'dataType' : 'json', 'url' : '${rc.contextP ...

  4. 通过ApplicationContext.getBean(beanName)动态加载数据。

    一,前台数据 $.ajax({ url: '/intra/coe/order/ploadTable.htm', type: 'POST', dataType: 'json', data: {keyId ...

  5. JS显示动态的系统时间--JavaScript基础

    1.网页中实时显示当前时间 <!DOCTYPE html><html lang="en"><head> <meta charset=&qu ...

  6. 谈谈使用Redis缓存时批量删除的几种实现

    前言 在使用缓存的时候,我们时不时会遇到这样一个需求,根据缓存键的规则去批量删除这些数据,比较常见的就是按前缀去删除. 举个简单的例子,Redis中现在有几百个商品的数据,这些数据的key值是有一定规 ...

  7. Educational Codeforces Round 37

    Educational Codeforces Round 37 这场有点炸,题目比较水,但只做了3题QAQ.还是实力不够啊! 写下题解算了--(写的比较粗糙,细节或者bug可以私聊2333) A. W ...

  8. 【BZOJ3932】任务查询系统(主席树)

    [BZOJ3923]任务查询系统(主席树) 题面 Description 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei ...

  9. tomcat环境配置 Linux 与 Windows

    Windows:例如tomcat安装在 E:\tomcat 需要在环境变量配置:CATALINA_HOME=E:\tomcat CATALINA_BASE= E:\tomcat Linux:例如tom ...

  10. 论文笔记(7):Constrained Convolutional Neural Networks for Weakly Supervised Segmentation

    UC Berkeley的Deepak Pathak 使用了一个具有图像级别标记的训练数据来做弱监督学习.训练数据中只给出图像中包含某种物体,但是没有其位置信息和所包含的像素信息.该文章的方法将imag ...