1. 程式人生 > WINDOWS開發 >JDK8--09:全新的時間API

JDK8--09:全新的時間API

在JDK8之前,時間有各種問題,最大的問題就是,我們使用的時間格式化類SimpleDateFormat不是執行緒安全的

為了更準確的說明SimpleDateFormat非執行緒安全,演示一個併發做時間格式化的操作

    public void test() throws Exception{
        //全新的時間API   都不是執行緒安全的
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");

        Callable<Date> call = new Callable<Date>() {
            @Override
            
public Date call() throws Exception { return simpleDateFormat.parse("20200601"); } }; ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10); List<Future<Date>> list = new ArrayList<>(); for(int i=0;i<10;i++){ list.add(newFixedThreadPool.submit(call)); }
for (Future<Date> future : list){ log.info("============={}",future.get()); } newFixedThreadPool.shutdown(); }

執行結果:

技術分享圖片

就是因為多個執行緒併發使用SimpleDateFormat,因此出現了異常,那麼JDK8之前我們是如何保證SimpleDateFormat執行緒安全的呢?

在JDK8之前,我們使用ThreadLocal鎖定SimpleDateFormat,來保證執行緒安全:

首先,建立一個使用TreadLocal鎖定的SimpleDateFormat

public class DateFormatThreadLocal {
    private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyyMMdd");
        }
    };

    public static Date convert(String source) throws Exception{
        return df.get().parse(source);
    }
}

其次,在做時間格式化時,使用該類中鎖定的SimpleDateFormat物件

    @Test
    public void test2() throws Exception{
        //全新的時間API   都不是執行緒安全的
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");

        Callable<LocalDate> call = new Callable<LocalDate>() {
            @Override
            public LocalDate call() throws Exception {
                return LocalDate.parse("20200601",dateTimeFormatter);
            }
        };

        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);

        List<Future<LocalDate>> list = new ArrayList<>();

        for(int i=0;i<10;i++){
            list.add(newFixedThreadPool.submit(call));
        }

        for (Future<LocalDate> future : list){
            log.info("============={}",future.get());
        }

        newFixedThreadPool.shutdown();
    }

執行結果:

技術分享圖片

可以發現,程式執行正常,均做了格式化

那麼在JDK8中,提供了全新的時間類,這些類都是執行緒安全的,可以在多執行緒併發時使用,無需擔心執行緒安全問題,無需建立ThreadLocal鎖定的格式化類

首先,介紹JDK8中新增的時間類 :LocalDate LocalTime LocalDateTime

1、時間物件建立

  (1)可以使用now建立時間類,時間為當前時間

  (2)可以使用of建立指定時間

        //建立日期
        //LocalDate  LocalTime  LocalDateTime
        LocalDateTime ld = LocalDateTime.now();
        log.info("LocalDateTime.now()=================={}",ld);

        LocalDateTime ld1 = LocalDateTime.of(2020,5,31,13,25,36);
        log.info("LocalDateTime=================={}",ld1);

2、日期運算:JDK8提供了plus*方法可以對日期進行運算操作

        //日期運算  plus 加日期
        LocalDateTime ld = LocalDateTime.now();
        log.info("plusDays=================={}",ld.plusDays(2));
        log.info("plusHours=================={}",ld.plusHours(3));

3、get返回值:JDK8提供了get*方法可以返回年份、月份、日期等

        //get 返回值
        LocalDateTime ld = LocalDateTime.now();
        log.info("getMonthValue=================={}",ld.getMonthValue());
        log.info("getDayOfMonth=================={}",ld.getDayOfMonth());

4、時間戳操作

  上面提到的LocalDate、LocalTime、LocalDateTime輸出的都是指定的時間格式,但是如果我們需要使用時間戳,就需要其他的方式處理,對於時間戳的操作如下:

  (1)直接獲取時間戳,此時獲取的時間戳為UTC時間,即世界協調時間(世界標準時間,以北京時間為例,由於北京時間採取的是東八區時間,因此是標準時間+8個小時,即為北京時間)

  (2)獲取指定時區的時間戳

  (3)轉換為時間戳

  (4)指定時間戳起始時間(1970-01-01 00:00:00)後多少時間的時間戳

        //時間戳
        Instant instant = Instant.now();//預設是UTC時間(世界協調時間)
        log.info("時間戳===={}",instant);
        //轉換為東八區時間(北京時間)
        OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
        log.info("轉換為東八區時間(北京時間)=={}",offsetDateTime);
        //轉換為時間戳
        long toEpochMilli = instant.toEpochMilli();
        log.info("時間戳=={}",toEpochMilli);

        //改變時間   距離時間戳的時間  1970-01-01 00:00:00
        Instant instant1 = Instant.ofEpochSecond(60*23);
        log.info("距離時間戳的時間=={}",instant1);

執行結果:

技術分享圖片

通過執行結果可以發現,時間戳和根據時區獲取的時間戳輸出時,仍然是格式化過的時間格式,且是由使用東八區的時間時,才與我電腦上的北京時間一致,同時輸出的時間上有+08:00的輸出;

另外,對於最後一個距離時間戳的時間,我們設定了60*23,即設定了23分鐘,最後輸出時間為:1970-01-01T00:23:00Z

5、計算時間差

  (1)Duration:獲取兩個時間的時間差

  (2)Period:獲取兩個日期的間隔

        //計算時間差
        //Duration:獲取兩個時間的時間差
        //Period:獲取兩個日期的間隔

        Instant instant2 = Instant.now();
        Thread.sleep(1000);
        Instant instant3 = Instant.now();
        Duration duration = Duration.between(instant2,instant3);

        log.info("duration.getSeconds()============={}",duration.getSeconds());
        log.info("duration.toMillis()============={}",duration.toMillis());
        log.info("duration.toHours()============={}",duration.toHours());

        LocalDate localDate = LocalDate.of(2018,10,6);
        LocalDate localDate1 = LocalDate.now();
        log.info("LocalDate.now()============={}",localDate1);
        Period period = Period.between(localDate,localDate1);
        log.info("period============={}",period);
        log.info("period.getYears()============={}",period.getYears());
        log.info("period.getMonths()============={}",period.getMonths());
        log.info("period.getDays()============={}",period.getDays());

執行結果:

技術分享圖片

  (1)我們在程式碼中,讓程式休眠了1秒,因此兩個時間相差的秒數為1,毫秒數為1001是因為程式執行使用了1毫秒

  (2)計算日期差時,我們使用了指定日期2018.10.6和當前日期(2020.6.2)做比較,輸出為P1Y7M27D,表示差了1年7個月27天,然後使用get*獲取時,獲取的即這個輸出的年、月、天

6、時間矯正器

  (1)可以獲取指定該年中的天數

  (2)獲取下一個指定的日期(下一個週日,JDK已提供)

  (3)獲取下一個工作日/結婚紀念日等(JDK未提供)

    /**
     * 時間矯正器
     */
    @Test
    public void test4(){
        //TemporalAdjuster:時間矯正器
        LocalDate localDate2 = LocalDate.now();
        log.info("LocalDate.now()============={}",localDate2);
        //指定時間
        LocalDate localDate3 = localDate2.withDayOfYear(55);
        log.info("withDayOfYear============={}",localDate3);
        //獲取下一個週日
        LocalDate localDate4 = localDate2.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
        log.info("TemporalAdjuster============={}",localDate4);
        //自定義 下一個工作日
        LocalDate localDate5 = localDate2.with((x)->{
            LocalDate localDate6 = (LocalDate) x;
            DayOfWeek dayOfWeek = localDate6.getDayOfWeek();
            switch (dayOfWeek){
                case FRIDAY:
                    return localDate6.plusDays(3);
                case TUESDAY:
                    return localDate6.plusDays(2);
                default:
                    return localDate6.plusDays(1);
            }
        });
        log.info("下一個工作日============={}",localDate5);
    }

輸出結果:

2020-06-02 15:08:26.457  INFO 19392 --- [           main] com.example.jdk8demo.Jdk8demoTest2       : LocalDate.now()=============2020-06-02
2020-06-02 15:08:26.461  INFO 19392 --- [           main] com.example.jdk8demo.Jdk8demoTest2       : withDayOfYear=============2020-02-24
2020-06-02 15:08:26.463  INFO 19392 --- [           main] com.example.jdk8demo.Jdk8demoTest2       : TemporalAdjuster=============2020-06-07
2020-06-02 15:08:26.464  INFO 19392 --- [           main] com.example.jdk8demo.Jdk8demoTest2       : 下一個工作日=============2020-06-04

7、時間格式化

    /**
     * 時間日期格式化
     *
     */
    @Test
    public void test6(){
        DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME;
        LocalDateTime ld = LocalDateTime.now();
        String date = ld.format(df);
        log.info("format==================={}",date);

        //自定義
        DateTimeFormatter df1 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH時mm分ss秒");
        date = ld.format(df1);
        log.info("format==================={}",date);
        //解析回原格式,此處要注意一點, HH時mm分ss秒大小寫一定注意,錯了就會反解析失敗
        LocalDateTime localDateTime = ld.parse(date,df1);
        log.info("parse==================={}",localDateTime);
    }

8、時區操作

  (1)獲取所有時區

  (2)根據時區獲取時間

  (3)計算時區的時間差

    /**
     * 時區操作
     * ZoneDate   ZoneTime    ZoneDateTime
     */
    @Test
    public void test7(){
        //獲取所有時區
        Set<String> set = ZoneId.getAvailableZoneIds();
        log.info("ZoneId.getAvailableZoneIds()================={}",JSON.toJSONString(set));
        //根據時區獲取時間
        LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Pacific/Fiji"));
        LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        log.info("根據時區獲取日期=========Pacific/Fiji==={}==========Asia/Shanghai==={}",localDateTime,localDateTime1);
        //獲取時差
        ZonedDateTime zonedDateTime = localDateTime1.atZone(ZoneId.of("Pacific/Fiji"));
        log.info("Shanghai與Fiji的時差========{}",zonedDateTime);
    }