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的值被不同的线程操作,从而导致不可预料的数据处理错误。

posted @   畔山陆仁贾  阅读(657)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示