介绍简单数据格式是JDK提供的rt包下的时间格式转换的类,类名为java.text.SimpleDateFormat。
从这个班的评论中可以看出:
1、可以将date转换为文本格式
2、可以将文本转换为date格式
3、可以针对不同的时区进行转换
4、允许你采用定制的时间patter。 可以使用applyPattern方法更改转换格式
他自己提供了部分字母含义,但留下了’a’to’z’和’a’to’z’所有未定义的文字,已经提供如下。
这里需要注意的是大小写的区别。 例如,y和y,m和m。 有各种各样的值范围,如h和k、k和h。
常见使用方法:
publicclasssimpledateformattest { publicstaticvoidmain [ ] args } { try { simpledateformatsdf _ text2date=newsimpling datedate System.out.println ('转换后的时间date:' date ); simpledateformatsdf _ date2 text=newsimpledateformat (' yyyy _ mm _ ddhh-mm-ss ' ); string datestring=SDF _ date2 text.format (date; System.out.println ('转换后的时间String:' dateString ); }catch(parseexceptione ) { System.out.println )日期转换错误: ' e ); }}输出结果:
转换后的时间date : wedmar 0309336049336012 CST 2021
转换后的时间String:2021_03_03 09-49-12
常见日期和字符串之间的格式转换可以通过简单日期格式提供的parse、format方式实现。
言归正传,为什么他们是非线程且安全呢?
在官方文件中,关于是否同时安全的一栏中说:
同步化
dateformatsarenotsynchronized.itisrecommendedtocreateseparateformatinstancesforeachthread.ifmultiplethreadsaccessaformatcococod
正如第一句中所说,时间格式转换是非线程且安全的。 (第一种情况是为每个线程创建SimpleDateFormat对象(官方推荐)。 (第二种情况)如果在并发环境中使用简单数据格式对象,则需要同步串行。
脑洞大:
文档中说Date formats是非线程安全的,但是parse方法是线程安全的吗?
一起看看吧!
源代码的解密首先从格式开始。 示例代码:
simpledateformatsdf _ date2 text=newsimpledateformat (' yyyy _ mm _ ddhh-mm-ss ' ); string datestring=SDF _ date2 text.format (date; publicfinalstringformat (datedate ) return format (date,new StringBuffer ),DontCareFieldPosition.INSTANCE ).tostrince
示例代码使用第一个,无论是第一个还是第三个,最终调用第二个。 第三,可以输入Number类型,即时间戳。
调用的format是一种抽象的方法,提供您自定义的实现:
publicabstractstringbufferformat (date,StringBuffer toAppendTo,字段位置字段位置); 让我们看看在简单日期格式中的实现。
@Override publi
c StringBuffer format(Date date, StringBuffer toAppendTo,FieldPosition pos){ // 指定解析的索引位置 pos.beginIndex = pos.endIndex = 0; return format(date, toAppendTo, pos.getFieldDelegate()); } // Called from Format after creating a FieldDelegate private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) { // 并发问题出现的关键,对全局成员变量calendar设置为date时间,后续时间取值从calendar中取 calendar.setTime(date); for (int i = 0; i < compiledPattern.length; ) { // 对时间模板进行解析 ... 具体细节暂时忽略 } return toAppendTo; }我们主要关注一下上面代码中的:
// 并发问题出现的关键,对全局成员变量calendar设置为date时间,后续时间取值从calendar中取calendar.setTime(date);其中calendar是对象的成员变量,
/** * The {@link Calendar} instance used for calculating the date-time fields * and the instant of time. This field is used for both formatting and * parsing. */ protected Calendar calendar;查看注释,我们发现,他说这个字段会用来formatting and parsing,那么估计在parse的时候也是有同样问题的了。
趁热打铁,我们看下parse代码:
SimpleDateFormat sdf_text2date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date date = sdf_text2date.parse("2021-03-03 09:49:12"); public Date parse(String source) throws ParseException { ParsePosition pos = new ParsePosition(0); Date result = parse(source, pos);// ...省略无用细节 return result; }也是一个抽象方法,供我们拓展:
public abstract Date parse(String source, ParsePosition pos);我们查看SimpleDateFormat的实现
@Override public Date parse(String text, ParsePosition pos) { int start = pos.index; int oldStart = start; int textLength = text.length(); boolean[] ambiguousYear = {false}; CalendarBuilder calb = new CalendarBuilder(); for (int i = 0; i < compiledPattern.length; ) { //....解析过程,省略,主要是解析出不同时间域的数值到CalendarBuilder 对象中 } } Date parsedDate; // 关键点,将CalendarBuilder中解析出的时间域设置发布到,成员变量calendar中 parsedDate = calb.establish(calendar).getTime(); return parsedDate; }可见,在parse方法中,也是会将最终解析的时间设置到成员变量calendar中,仍然存在并发问题。
并发问题分析看了上面的format与parse方法,我们分析一下这个并发问题,因为calendar是SimpleDateFormat类的一个普通成员变量,那么一个SimpleDateFormat对象就对应一个calendar对象,当在多线程环境下,如当前有线程A,B同时在format操作,A线程在设置完date之后开始解析:
calendar.setTime(date);在A线程解析到一半时,B线程也统一使用该SimpleDateFormat对象,将calendar的值设置成另一个时间,那么此时A线程解析时读取的剩余的时间就是B线程设置的值了。parse方法也是同理。
同时,根据代码发现,parse与format方法,是共用一个calendar成员变量的,所以如果多线程共用SimpleDateFormat对象,即使A线程做format操作,B线程做parse操作,也会存在并发问题。
错误代码展示为了方便你更快复现这个问题,我写了一个测试:
@Test public void testSimpleDateFormatUnSynchronized() { DateFormat sdf_date2text = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date1 = new Date(1614820308016L); Date date2 = new Date(1615820309016L); /** 输出 * 初始定义时间1: 2021-03-04 09:11:48 * 初始定义时间2: 2021-03-15 22:58:29 */ System.out.println("初始定义时间1: " + sdf_date2text.format(date1)); System.out.println("初始定义时间2: " + sdf_date2text.format(date2)); Thread t1 = new Thread(() -> { int i = 0; while (i<100) { String dateString = sdf_date2text.format(date1); if (!dateString.equals("2021-03-04 09:11:48")) { System.out.println(Thread.currentThread().getName() + "异常时间: " + dateString); } i++; } },"线程1"); Thread t2 = new Thread(() -> { int i = 0; while (i<100) { String dateString = sdf_date2text.format(date2); if (!dateString.equals("2021-03-15 22:58:29")) { System.out.println(Thread.currentThread().getName() + "异常时间: " + dateString); } i++; } },"线程2"); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } }初始定义时间1: 2021-03-04 09:11:48
初始定义时间2: 2021-03-15 22:58:29
线程1异常时间: 2021-03-15 22:58:29
线程1异常时间: 2021-03-04 22:58:29
线程2异常时间: 2021-03-04 22:58:29
线程1异常时间: 2021-03-04 22:58:29
线程2异常时间: 2021-03-04 09:11:48
线程1异常时间: 2021-03-15 22:58:29
线程1异常时间: 2021-03-15 22:58:29
线程1异常时间: 2021-03-15 22:58:29
我们可以看到,在非线程安全情况下并发访问SimpleDateFormat对象,出现的结果就是混乱的,甚至线程1得到的是线程2的时间,这样整个业务系统就出bug了。