Java8中LocalDate详解&Date线程不安全的原因

2022-11-01 13:57:48

LocalDate

分类分工

java.time.LocalDate->只对年月日做出处理java.time.LocalTime->只对时分秒纳秒做出处理java.time.LocalDateTime->同时可以处理年月日和时分秒

优点

除了使用起来更加简单和灵活,主要是传统的时期处理类Date、Calendar不是多线程安全的,而LocalDate 线程安全的,所以不用担心并发问题。

实际使用

importjava.time.LocalDate;importjava.time.LocalDateTime;importjava.time.LocalTime;importjava.time.ZoneId;importjava.time.format.DateTimeFormatter;importjava.time.temporal.ChronoUnit;importjava.util.List;importjava.util.stream.Collectors;importjava.util.stream.Stream;importcom.google.common.collect.Lists;/**
 * Java 8 的时间工具类
 */publicclassDateUtils{/**
     * 默认使用系统当前时区
     */privatestaticfinalZoneId ZONE=ZoneId.systemDefault();privatestaticfinalString DATE_FORMAT="yyyy-MM-dd";privatestaticfinalString DATE_FORMAT_DS="yyyyMMdd";privatestaticfinalString DATE_FORMAT_DEFAULT="yyyy-MM-dd HH:mm:ss";privatestaticfinalString TIME_FORMAT="yyyyMMddHHmmss";privatestaticfinalString REGEX="\\:|\\-|\\s";publicstaticfinalDateTimeFormatter FORMATTER=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");/**
     * 获取当前时间
     *
     * @param format
     * @return
     */publicstaticStringgetCurrentTime(String format){DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern(format);LocalDateTime now=LocalDateTime.now();return now.format(dateTimeFormatter);}/**
     * 获取昨日时间
     *
     * @param format
     * @return
     */publicstaticStringgetYesterday(String format){DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern(format);LocalDate nowDate=LocalDate.now();LocalDate yesterday= nowDate.minusDays(1);return yesterday.format(dateTimeFormatter);}/**
     * 获取上周的时间
     *
     * @param format
     * @return
     */publicstaticStringgetLastWeek(String format){DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern(format);LocalDate nowDate=LocalDate.now();LocalDate lastWeek= nowDate.minusWeeks(1);return lastWeek.format(dateTimeFormatter);}/**
     * 获取上个月的时间
     *
     * @param format
     * @return
     */publicstaticStringgetLastMonth(String format){DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern(format);LocalDate nowDate=LocalDate.now();LocalDate lastMonth= nowDate.minusMonths(1);return lastMonth.format(dateTimeFormatter);}/**
     * 获取去年的时间
     *
     * @param format
     * @return
     */publicstaticStringgetLastYear(String format){DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern(format);LocalDate nowDate=LocalDate.now();LocalDate lastYear= nowDate.minusYears(1);return lastYear.format(dateTimeFormatter);}/**
     * 获取前多少天的日期
     *
     * @param format
     * @param num
     * @return
     */publicstaticStringgetBeforeSomeDay(String format,int num){DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern(format);LocalDate nowDate=LocalDate.now();LocalDate beforeDay= nowDate.minusDays(num);return beforeDay.format(dateTimeFormatter);}/**
     * 获取指定时间的前多少天
     *
     * @param format
     * @param date
     * @param num
     * @return
     */publicstaticStringgetBeforeDayOfDate(String format,String date,int num){DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern(format);LocalDate localDate=LocalDate.parse(date, dateTimeFormatter);LocalDate beforeDay= localDate.minusDays(num);return beforeDay.format(dateTimeFormatter);}/**
     * 获取当天的开始时间  yyyy-MM-dd 00:00:00
     *
     * @param format
     * @return
     */publicstaticStringgetDayStartTime(String format,String date){DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern(format);LocalDate localDate=LocalDate.parse(date, dateTimeFormatter);LocalDateTime toDayStart=LocalDateTime.of(localDate,LocalTime.MIN);return toDayStart.format(FORMATTER);}/**
     * 获取当天的结束时间 yyyy-MM-dd 23:59:59
     *
     * @param format
     * @return
     */publicstaticStringgetDayEndTime(String format,String date){DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern(format);LocalDate localDate=LocalDate.parse(date, dateTimeFormatter);LocalDateTime toDayStart=LocalDateTime.of(localDate,LocalTime.MAX);return toDayStart.format(FORMATTER);}/**
     * 获取两个时间之间的间隔天数
     *
     * @param startDate yyyyMMdd
     * @param endDate   yyyyMMdd
     * @return
     */publicstaticlonggetRangeCountOfDate(String startDate,String endDate){DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern(DATE_FORMAT_DS);LocalDate startLocalDate=LocalDate.parse(startDate, dateTimeFormatter);LocalDate endLocalDate=LocalDate.parse(endDate, dateTimeFormatter);long count=ChronoUnit.DAYS.between(startLocalDate, endLocalDate);return count;}/**
     * 后期两个时间之间的所有日期 【包含开始时间和结束时间】
     *
     * @param startDate yyyyMMdd
     * @param endDate   yyyyMMdd
     * @return
     */publicstaticList<String>getRangeOfDate(String startDate,String endDate){List<String> range=Lists.newArrayList();DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern(DATE_FORMAT_DS);LocalDate startLocalDate=LocalDate.parse(startDate, dateTimeFormatter);LocalDate endLocalDate=LocalDate.parse(endDate, dateTimeFormatter);long count=ChronoUnit.DAYS.between(startLocalDate, endLocalDate);if(count<0){return range;}
 
        range=Stream.iterate(startLocalDate, d-> d.plusDays(1)).limit(count+1).map(
            s-> s.format(dateTimeFormatter)).collect(Collectors.toList());return range;}publicstaticvoidmain(String[] args){System.out.println(getRangeOfDate("20191010","20191020"));}}

Date线程不安全的原因

1- SimpleDateFormat线程安全问题

使用ExecutorService提交多个任务的方式,模拟并发环境将字符串转换为日期即测试parse方法,代码如下:

@TestpublicvoidtestParse(){ExecutorService executorService=Executors.newCachedThreadPool();List<String> dateStrList=Lists.newArrayList("2018-04-01 10:00:01","2018-04-02 11:00:02","2018-04-03 12:00:03","2018-04-04 13:00:04","2018-04-05 14:00:05");SimpleDateFormat simpleDateFormat=newSimpleDateFormat("yyyy-MM-dd hh:mm:ss");for(String str: dateStrList){
        executorService.execute(()->{try{
                simpleDateFormat.parse(str);TimeUnit.SECONDS.sleep(1);}catch(Exception e){
                e.printStackTrace();}});}}

运行后,报错如下:
在这里插入图片描述
可见并发环境下使用SimpleDateFormat的parse方法有线程安全问题!

线程安全问题的原因:

在SimpleDateFormat转换日期是通过Calendar对象来操作的,SimpleDateFormat继承DateFormat类,DateFormat类中维护一个Calendar对象,代码如下:
在这里插入图片描述
在这里插入图片描述
通过DateFormat类中的注释可知:此处Calendar实例被用来进行日期-时间计算,既被用于format方法也被用于parse方法!

在parse方法的最后,会调用CalendarBuilder的establish方法,入参就是SimpleDateFormat维护的Calendar实例,在establish方法中会调用calendar的clear方法,如下:
在这里插入图片描述
可知SimpleDateFormat维护的用于format和parse方法计算日期-时间的calendar被清空了,如果此时线程A将calendar清空且没有设置新值,线程B也进入parse方法用到了SimpleDateFormat对象中的calendar对象,此时就会产生线程安全问题!

2- 解决方案

**每一个使用SimpleDateFormat对象进行日期-时间进行format和parse方法的时候就创建一个新的SimpleDateFormat对象,用完就销毁即可!**代码如下:

/**
 * 模拟并发环境下使用SimpleDateFormat的parse方法将字符串转换成Date对象
 */@TestpublicvoidtestParseThreadSafe(){ExecutorService executorService=Executors.newCachedThreadPool();List<String> dateStrList=Lists.newArrayList("2018-04-01 10:00:01","2018-04-02 11:00:02","2018-04-03 12:00:03","2018-04-04 13:00:04","2018-04-05 14:00:05");for(String str: dateStrList){
        executorService.execute(()->{try{//创建新的SimpleDateFormat对象用于日期-时间的计算SimpleDateFormat simpleDateFormat=newSimpleDateFormat("yyyy-MM-dd hh:mm:ss");
                simpleDateFormat.parse(str);TimeUnit.SECONDS.sleep(1);
                simpleDateFormat=null;//销毁对象}catch(Exception e){
                e.printStackTrace();}});}}

在运行可以发现不会报出之前的错误了!

综上所述,使用SimpleDateFormat对象进行日期-时间计算时,如果SimpleDateFormat是多个线程共享的就会有线程安全问题!应该让每一个线程都有一个独立的SimpleDateFormat对象用于日期-时间的计算!此时就可以使用ThreadLocal将SimpleDateFormat绑定到线程上,是的该线程上的日期-时间计算顺序的使用SimpleDateFormat对象,这样也可以避免线程安全问题!

另外就是使用LocalDateTime

LocalDateTime now=LocalDateTime.of(LocalDate.now(),LocalTime.now());DateTimeFormatter fmt=DateTimeFormatter.ofPattern("yyyyMMddHHmmss");String dateStr= now.format(fmt);
  • 作者:Archie_java
  • 原文链接:https://blog.csdn.net/qq_43842093/article/details/127456082
    更新时间:2022-11-01 13:57:48