最近在项目中,涉及到日期转换以及多线程的一些问题。如果不涉及到多线程,则两者都可放心大胆的用。但是涉及到多线程的问题,就得注意下了,SimpleDateFormat是线程不安全。
1.何谓线程不安全?
多线程的执行并不是一个线程一个线程的顺序执行,而是相互抢夺cpu资源,拿到资源的就会执行。正式这个特点,如果这两个线程有一个公共的变量,一个线程读取变量并对变量做修改,那么另外一个线程两次访问的变量就会不同。
2.为什么simpleDateFormat是线程不安全?
我们先得看simpleDateFormat的源码
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition pos)
{
// 如此轻易地使用内部变量,肯定不能线程安全
// 线程都对pos进行写操作,必然会影响其他线程的读操作
pos.beginIndex = pos.endIndex = 0;
return format(date, toAppendTo, pos.getFieldDelegate());
}
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
// 这里已经彻底毁坏线程的安全性,这里的calendar变量是继承自父类DateFormat
calendar.setTime(date);
boolean useDateFormatSymbols = useDateFormatSymbols();
for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
}
switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;
case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break;
default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}
3.解决办法:
- 加线程同步锁:synchronized(lock),缺点:执行效率低下
- 将SimpleDateFormat设置成局部变量,缺点:每次调用都会创建对象,结束之后还要清理
- 使用DateTimeFormatter代替
4.DateTimeFormatter的用法
/**
* 从Java 8开始,java.time包提供了新的日期和时间API,主要涉及的类型有:
* 本地日期和时间:LocalDateTime,LocalDate,LocalTime;
* 带时区的日期和时间:ZonedDateTime;
* 时刻:Instant;
* 时区:ZoneId,ZoneOffset;
* 时间间隔:Duration。
* 以及一套新的用于取代SimpleDateFormat的格式化类型DateTimeFormatter。
*/
public static void main(String[] args) {
// 指定日期和时间:
LocalDate d2 = LocalDate.of(2019, 11, 30); // 2019-11-30, 注意11=11月
LocalTime t2 = LocalTime.of(15, 16, 17); // 15:16:17
LocalDateTime dt2 = LocalDateTime.of(2019, 11, 30, 15, 16, 17);
LocalDateTime dt3 = LocalDateTime.of(d2, t2);
DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(dtf.format(LocalDateTime.now()));//2020-06-18 00:47:32
LocalDateTime ldt=LocalDateTime.parse("2019-05-12 12:00:00",dtf);//2019-05-12T12:00
/**
* 注意ISO 8601规定的日期和时间分隔符是T。标准格式如下:
* 日期:yyyy-MM-dd
* 时间:HH:mm:ss
* 带毫秒的时间:HH:mm:ss.SSS
* 日期和时间:yyyy-MM-dd'T'HH:mm:ss
* 带毫秒的日期和时间:yyyy-MM-dd'T'HH:mm:ss.SSS
*/
// 本月第一天0:00时刻:
LocalDateTime firstDay = LocalDate.now().withDayOfMonth(1).atStartOfDay();
System.out.println(firstDay);
// 本月最后1天:
LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
System.out.println(lastDay);
// 下月第1天:
LocalDate nextMonthFirstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println(nextMonthFirstDay);
//要判断两个LocalDateTime的先后,可以使用isBefore()、isAfter()方法,对于LocalDate和LocalTime类似
LocalDateTime target=LocalDateTime.of(2020,6,10,22,10,10);
System.out.println(LocalDateTime.now().isAfter(target));//true
System.out.println(LocalDateTime.now().isBefore(target));//false
/**
* 注意到LocalDateTime无法与时间戳进行转换,因为LocalDateTime没有时区,无法确定某一时刻。
* ZonedDateTime相当于LocalDateTime加时区的组合,它具有时区,可以与long表示的时间戳进行转换。
*/
System.out.println(ZoneId.systemDefault());//Asia/Shanghai
System.out.println(LocalDateTime.now().atZone(ZoneId.of("America/New_York")));
}
}
参考自https://www.liaoxuefeng.com/wiki/1252599548343744/1303904694304801