JDK8日期格式化类DateTimeFormatter
这是几年前写的旧文,此前发布Wordpress小站上,现在又重新整理。算是温故知新,后续会继续整理。如有错误望及时指出,在此感谢。
为何要引入新的日期处理API?
在JDK8,增加了日期处理格式化类java.time.format.DateTimeFormatter。
既然之前已经提供了SimpleDateFormat,为何又要提供DateTimeFormatter呢?有何不同呢。
我们看下类的注释
在DateTimeFormatter类的注释中写到:
This class is immutable and thread-safe.
作为对比,我们看下在此之前JDK中提供的java.text.SimpleDateFormat:
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
最大的不同,就是SimpleDateFormat不是线程安全的,而DateTimeFormatter是线程安全的。
同时在DateTimeFormatter还提供了其它更方便易用的API,以及相关的配套类,提供更强大的能力。
那么这次我们就以线程安全为目标,进行验证。
还在使用SimpleDateFormat的同学要及时更新到DateTimeFormatter啦,以免在多线程下处理日期时产生不可预知的问题。
JDK环境:1.8
SimpleDateFormat多线程下的日期处理-错误用例
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateFormatThreadTest1 {
public static void main(String[] args) throws ParseException {
String pattern = "yyyy-MM-dd HH:mm:ss";
SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
// String format = dateFormat.format(new Date());
// System.out.println(format);
//
// Date parse = dateFormat.parse("2018-05-01 12:01:20");
// System.out.println(parse);
new Thread(() -> {
try {
Date d1 = dateFormat.parse("2018-05-01 12:00:00");
System.out.println("thread:" + Thread.currentThread().getName() + ",初始化时间:" + dateFormat.format(d1));
for (int i = 0; i < 10; i++) {
try {
d1 = new Date(d1.getTime() + 1000);//加1秒
String outputStr = dateFormat.format(d1);
System.out.println("thread:" + Thread.currentThread().getName() + ",outputStr:" + outputStr);
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
} catch (ParseException e) {
throw new RuntimeException(e);
}
}, "t1").start();
new Thread(() -> {
try {
Date d1 = dateFormat.parse("2018-05-01 12:00:00");
System.out.println("thread:" + Thread.currentThread().getName() + ",初始化时间:" + dateFormat.format(d1));
for (int i = 0; i < 10; i++) {
try {
d1 = new Date(d1.getTime() - 1000);//减1秒
String outputStr = dateFormat.format(d1);
System.out.println("thread:" + Thread.currentThread().getName() + ",outputStr:" + outputStr);
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
} catch (ParseException e) {
throw new RuntimeException(e);
}
}, "t2").start();
// thread:t1,初始化时间:2018-05-01 12:00:00
// thread:t2,初始化时间:2018-05-01 12:00:00
// thread:t1,outputStr:2018-05-01 11:59:59 //异常
// thread:t2,outputStr:2018-05-01 11:59:59
// thread:t1,outputStr:2018-05-01 12:00:02
// thread:t2,outputStr:2018-05-01 11:59:58
// thread:t1,outputStr:2018-05-01 12:00:03
// thread:t2,outputStr:2018-05-01 11:59:57
// thread:t1,outputStr:2018-05-01 12:00:04
// thread:t2,outputStr:2018-05-01 11:59:56
// thread:t1,outputStr:2018-05-01 12:00:05
// thread:t2,outputStr:2018-05-01 11:59:55 //异常
// thread:t1,outputStr:2018-05-01 11:59:54 //异常
// thread:t2,outputStr:2018-05-01 11:59:54 //异常
// thread:t1,outputStr:2018-05-01 11:59:53 //异常
// thread:t2,outputStr:2018-05-01 11:59:53 //异常
// thread:t2,outputStr:2018-05-01 11:59:52 //异常
// thread:t1,outputStr:2018-05-01 11:59:52 //异常
// thread:t2,outputStr:2018-05-01 12:00:09 //异常
// thread:t1,outputStr:2018-05-01 12:00:09 //异常
// thread:t2,outputStr:2018-05-01 12:00:10 //异常
// thread:t1,outputStr:2018-05-01 12:00:10 //异常
}
}
DateTimeFormatter多线程下的日期处理-正确用例
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateFormatThreadTest2 {
public static void main(String[] args) {
String pattern = "yyyy-MM-dd HH:mm:ss";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
// String date2Str = formatter.format(LocalDateTime.now());
// System.out.println(date2Str);
//
// TemporalAccessor parse = formatter.parse("2018-05-01 12:01:20");
// LocalDateTime str2Date = LocalDateTime.from(parse);
// LocalDateTime localDateTime = str2Date.plusHours(8);
// System.out.println(localDateTime);
new Thread(() -> {
LocalDateTime str2Date = LocalDateTime.from(formatter.parse("2018-05-01 12:01:20"));
System.out.println("thread:" + Thread.currentThread().getName() + ",初始化时间:" + str2Date);
for (int i = 0; i < 10; i++) {
str2Date = str2Date.plusSeconds(1);//加1秒
System.out.println("thread:" + Thread.currentThread().getName() + ",outputStr:" + str2Date);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "t1").start();
new Thread(() -> {
LocalDateTime str2Date = LocalDateTime.from(formatter.parse("2018-05-01 12:01:20"));
System.out.println("thread:" + Thread.currentThread().getName() + ",初始化时间:" + str2Date);
for (int i = 0; i < 10; i++) {
str2Date = str2Date.plusSeconds(-1);//减1秒
System.out.println("thread:" + Thread.currentThread().getName() + ",outputStr:" + str2Date);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "t2").start();
// thread:t2,初始化时间:2018-05-01T12:01:20
// thread:t2,outputStr:2018-05-01T12:01:19
// thread:t1,初始化时间:2018-05-01T12:01:20
// thread:t1,outputStr:2018-05-01T12:01:21
// thread:t2,outputStr:2018-05-01T12:01:18
// thread:t1,outputStr:2018-05-01T12:01:22
// thread:t2,outputStr:2018-05-01T12:01:17
// thread:t1,outputStr:2018-05-01T12:01:23
// thread:t2,outputStr:2018-05-01T12:01:16
// thread:t1,outputStr:2018-05-01T12:01:24
// thread:t2,outputStr:2018-05-01T12:01:15
// thread:t1,outputStr:2018-05-01T12:01:25
// thread:t2,outputStr:2018-05-01T12:01:14
// thread:t1,outputStr:2018-05-01T12:01:26
// thread:t2,outputStr:2018-05-01T12:01:13
// thread:t1,outputStr:2018-05-01T12:01:27
// thread:t2,outputStr:2018-05-01T12:01:12
// thread:t1,outputStr:2018-05-01T12:01:28
// thread:t2,outputStr:2018-05-01T12:01:11
// thread:t1,outputStr:2018-05-01T12:01:29
// thread:t2,outputStr:2018-05-01T12:01:10
// thread:t1,outputStr:2018-05-01T12:01:30
}
}
总结
新版本的日期处理API功能上非常强大,同时可以在多线程下进行日期处理时保持线程安全的能力,这是因为每次进行日期操作,都是返回一个新的日期对象。以 LocalDateTime.plusSeconds方法为例,内部实现是直接返回一个新的LocalDateTime对象:
new LocalDateTime(newDate, newTime)
而SimpleDateFormat是在内部维护了一个Calendar对象,而calendar本身不是线程安全的,在多线程下同时进行日期操作时,线程会因为CPU在执行时产生的线程上下文切换,或者Calendar的值被不同的线程操作,从而导致不可预料的数据处理错误。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!