在日常开发中,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. JLINK(SEGGER)灯不亮 USB不识别固件修复、clone修改

    今天调SMT32插拔几下,JLINK竟然挂掉了网上找了这个教程,搞了半天才搞好,驱动没装好!WIN7系统,自动安装的驱动是GPS.COM10,郁闷,错误来的.应该是:atm6124.sys.要手动选择 ...

  2. 字符串压缩 stringZip

    1,题目描述 通过键盘输入一串小写字母(a~z)组成的字符串.请编写一个字符串压缩程序,将字符串中连续出席的重复字母进行压缩,并输出压缩后的字符串.压缩规则:1. 仅压缩连续重复出现的字符.比如字符串 ...

  3. Struts2实现文件上传报错(三)

    1.具体错误如下 usage: java org.apache.catalina.startup.Catalina [ -config {pathname} ] [ -nonaming ] { -he ...

  4. app_offline.htm的作用

    如果你要COPY站点,进行站点维护,部署,和进行大量修改,有可能要停掉你的WEB应用程序了,而以一个友好的方式提示给用户,比如什么"本网站正在更新"等等的信息可以建立一个叫app_ ...

  5. 把连续动态bmp转换为avi

    把动态bmp24转换为avi BYTE tmp_buf[1024*768*4]; //生成avi void BMPtoAVI(CString szAVIName, CString strBmpDir) ...

  6. 表格布局----基于bootstrap样式 布局

    在实际开发中,我们通过菜鸟教程复制的表格往往不能满足我们的开发需求,样式很难看,而且不能自适应,尤其是需要到处Excel的样式,感觉非常糟糕,这次我就写了一个表单,不足之处,希望大神们多多指教: 代码 ...

  7. Car HDU - 5935

    Problem Description Ruins is driving a car to participating in a programming contest. As on a very t ...

  8. 《C#图解教程》 总览

    初识本书是在知乎,许多网友推荐它作为 C# 入门书籍. 本书的最大特点是插图丰富,许多复杂的概念,一副插图就解释得通透明了. 本书另外一个隐藏特性是作者有着 C/C++ 经验,书中经常提到它们与 C# ...

  9. C#多线程编程(5)--线程安全1

    当你需要2个线程读写同一个数据时,就需要数据同步.线程同步的办法有:(1)原子操作:(2)锁.原子操作能够保证该操作在CPU内核中不会被"拆分",锁能够保证只有一个线程访问该数据, ...

  10. 【BZOJ1216】操作系统(堆,模拟)

    [BZOJ1216]操作系统(堆,模拟) 题面 题目描述 写一个程序来模拟操作系统的进程调度.假设该系统只有一个CPU,每一个进程的到达时间,执行时间和运行优先级都是已知的.其中运行优先级用自然数表示 ...