java.text.DateFormat 多线程并发问题
在日常开发中,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() 方法,将 字符串转化成日期,也会有同样的非线程安全问题。
解决方案
- 每次进行格式化日期调用时,均 new 一个 SimpleDateFormat 对象;缺点是在高并发的情况下,就会频繁创建和销毁对旬,造成开销。
- 使用 synchronized 关键字 或 Lock 给静态方法加上同步;缺点是在高并发的情况下,所有的线程在此处会引起资源的竞争。
- 使用 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 多线程并发问题的更多相关文章
- java.text.DateFormat 日期格式化
一: java.text.DateFormat <%@ page language="java" contentType="text/html; charset=u ...
- 常用类一一时间处理相关类一一java.util.Tomezone(java.util.Calendar , java.util.Date , java.text.DateFormat)
时间处理相关类 时间是一个一维的东东.所以,我们需要一把刻度尺来区表达和度量时间.在计算机世界,我们把1970 年 1 月 1 日 00:00:00定为基准时间,每个度量单位是毫秒(1秒的千分之一). ...
- java.text.DateFormat 线程不安全问题
java.text下的 DateFormat 是线程不安全的: 建议1: 1.使用threadLocal包装DateFormat(太复杂,不推荐) 2.使用org.apache.commons.lan ...
- java 多线程:线程安全问题,示例DateFormat多线程执行冲突解决方案ThreadLocal、方法内变量
SimpleDateFormat多线程中执行报错 java.lang.NumberFormatException: For input string: "" import ja ...
- 【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. ...
- java.text.ParseException: Unparseable date: "2015-06-09 hh:56:19"
1.错误描述 [DEBUG:]2015-06-09 16:56:19,520 [-------------------transcation start!--------------] java.te ...
- java.text.SimpleDateFormat使用介绍
java.text.SimpleDateFormat的使用 java.lang.Object | +----java.text.Format | +-- ...
- java.text.ParseException: Failed to parse date ["未知']
先把"未知"替换为"" 直接new 出来的Gson 对象是无法解析为""的Date属性的,需要通过GsonBuilder来进行创建 Gson ...
- java.text.ParseException: Unparseable date: "2015-06-09 hh:56:19"
1.错误描写叙述 [DEBUG:]2015-06-09 16:56:19,520 [-------------------transcation start!--------------] java. ...
随机推荐
- 利用ICSharpCode.SharpZipLib进行压缩
#ZipLib is a Zip, GZip, Tar and BZip2 library written entirely in C# for the .NET platform. It is im ...
- RHEL简单管理SELINUX
Security Enhanced Linux(SELinux)是一个额外的系统安全层,主要目的是防止已遭泄露的系统服务访问用户数据. 对于一个服务来说,要关注SELinux的三个方面,一是文件SEL ...
- Django学习-6-路由系统
1.url(r'^index/', views.index), 函数处理 url(r'^home/', views.Home.as_view()), 类方法处理 ...
- TCP粘包、拆包及解决
TCP属于传输层的协议,传输层除了有TCP协议外还有UDP协议.那么UDP是否会发生粘包或拆包的现象呢?答案是不会.UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit来指示 ...
- PyCharm 2017.3 下载与安装
微信公众号:compassblog 欢迎关注.转发,互相学习,共同进步! 有任何问题,请后台留言联系! 1.下载 (1).下载链接: https://www.jetbrains.com/pycharm ...
- CSS3动画以及animation事件
1.CSS3动画以及animation事件的定义 animation :name duration timing-function delay iteration-count direction an ...
- webpack3配置字体图标和打包相关问题
webpak配置字体图标有两种方式 一.将字体图标和css打包到同一个文件中. 1.首先需要安装url-loader npm install --save-dev url-loader 2.相关配置如 ...
- Python Cookbook(第3版)中文版:15.18 传递已打开的文件给C扩展
15.18 传递已打开的文件给C扩展¶ 问题¶ 你在Python中有一个打开的文件对象,但是需要将它传给要使用这个文件的C扩展. 解决方案¶ 要将一个文件转换为一个整型的文件描述符,使用 PyFile ...
- Keras官方中文文档:序贯模型
快速开始序贯(Sequential)模型 序贯模型是多个网络层的线性堆叠,也就是"一条路走到黑". 可以通过向Sequential模型传递一个layer的list来构造该模型: f ...
- [HDU2296]Ring
vjudge Description For the hope of a forever love, Steven is planning to send a ring to Jane with a ...