概述

在Java8之前, 我们一般都是使用 SimpleDateFormat 来解析和格式化日期时间, 但它是线程不安全的。

    @Test
public void test() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
try {
Date date = sdf.parse("20191103091515");
System.out.println(date.toString());
} catch (ParseException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}

多次运行上面这段程序, 会报不同的异常, 下面是其中的一种

Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-4" Exception in thread "pool-1-thread-3" java.lang.NumberFormatException: empty String
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1867)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.java8.action.Demo.lambda$test$0(Demo.java:25)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

原因也很简单, 查看一下源码, 发现 SimpleDateFormat 类继承了父类 DateFormat 的成员变量  protected Calendar calendar; , 而Calendar 类没有被 final 修饰, 是可以被修改的。

回到上面这个问题, 看一下 SimpleDateFormat 的解析日期时间的API

进入 establish() 方法里面看一下

到此, 已经基本明了, 因为每次 SimpleDateFormat 解析日期时间都会清空一下它的成员变量 calendar 的值, 所以当多个线程并发访问同一个 SimpleDateformat 时, 就会有线程不安全问题。

解决方式也很简单, 你可以使用 ThreadLocal 类存放 SimpleDateFormat 对象, 让每个线程拥有自己的SimpleDateFormat对象。

    /**Map键对应不同的解析规则字符串, 比如yyyyMMdd*/
private static Map<String, ThreadLocal<SimpleDateFormat>> tl = new HashMap<>();

回到我们今天的主题, 在Java8中引入了新的日期和时间API, 这也是下面要介绍的内容。

新的日期时间类都被 final 修饰, 不存在想上面介绍的老版本API的线程不安全问题。

LocalDate、LocalTime和LocalDateTime

LocalDate和LocalTime, LocalDateTime 提供了许多静态工厂方法来创建它们的实例对象, 并且这三者之间可以很方便的互相进行类型转换。

    @Test
public void test() {
//静态方法创建对象
LocalDate ld = LocalDate.of(2019, 10, 3);
System.out.println(ld.getYear() + "\t" + ld.getMonth() + "\t" + ld.getDayOfMonth() + "\t" + ld.getDayOfWeek() + "\t" + ld.lengthOfMonth() + "\t" + ld.isLeapYear());//result: 2019 OCTOBER 3 THURSDAY 31 false
LocalDate now = LocalDate.now();
System.out.println(now.get(ChronoField.YEAR) + "\t" + now.get(ChronoField.MONTH_OF_YEAR) + "\t" + now.get(ChronoField.DAY_OF_MONTH));//result: 2019 11 3 LocalTime lt = LocalTime.of(20, 44, 12);
System.out.println(lt.getHour() + "\t" + lt.getMinute() + "\t" + lt.getSecond());//result: 20 44 12
//解析字符串
LocalDate ld2 = LocalDate.parse("2019-10-05");//默认格式: yyyy-MM-dd
System.out.println(ld2.toString());//result: 2019-10-05
LocalTime lt2 = LocalTime.parse("20:42:12.828");//默认格式: HH:mm:ss.SSS
System.out.println(lt2.toString());//result: 20:42:12.828
//互相进行类型转换
LocalDateTime ldt = LocalDateTime.of(2019, 10, 5, 21, 12, 10, 888).atZone(ZoneId.of("Asia/Shanghai")).toLocalDateTime();
LocalDateTime ldt2 = LocalDateTime.of(ld2, lt2);// 2019-10-05T20:42:12.828
LocalDateTime ldt3 = ld2.atTime(10, 10, 10);// 2019-10-05T10:10:10
LocalDateTime ldt4 = ld2.atTime(lt2);
LocalDateTime ldt5 = lt2.atDate(ld2);
LocalDateTime ldt6 = LocalDateTime.parse("2019/10/05 20:20:20.888", DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS"));
LocalDate ld6 = ldt6.toLocalDate();
LocalTime lt6 = ldt6.toLocalTime();
}

Instant

Instant对时间的建模方式是以UTC时区的1970年1月1日午夜时分开始所经历的秒数进行计算,它不包含时区信息。Instant类是为了方便计算机处理日期和时间而设计的。

Instant.now().toEpochMilli()  可以获取当前时间的时间戳, 另外, Instant 提供了类似 ofEpochMilli() 的方法根据某个时间戳获取 Instant 实例, isBefore()和isAfter() 则用来比较两个 Instant 的大小

    @Test
public void test2() {
long milli = Instant.now().toEpochMilli();//获取当前时间戳
Instant instant = Instant.ofEpochMilli(1572749169937L);//根据某个时间戳获取Instant实例
Instant instant2 = instant.minusSeconds(1000L);
System.out.println(instant.isAfter(instant2));//true
}

Duration和Period

Duration 对象用秒和纳秒来衡量时间的长短,如果想要对多个时间对象进行日期运算,可以用 Period 类

    @Test
public void test3() {
Duration d1 = Duration.between(LocalDateTime.of(2019, 10, 7, 15, 55, 55, 888), LocalDateTime.now());
Duration d2 = Duration.between(LocalTime.of(17, 55, 10), LocalTime.now());
Duration d3 = Duration.between(Instant.ofEpochMilli(1570544602000L), Instant.now());
System.out.println(d3.toHours());// 612
//Duration对象用秒和纳秒来衡量时间的长短,所以入参不能使用LocalDate类型, 否则抛UnsupportedTemporalTypeException: Unsupported unit: Seconds
//Duration.between(LocalDate.of(2019, 10, 7), LocalDate.now()); //如果想要对多个时间对象进行日期运算,可以用Period
Period p1 = Period.between(LocalDate.of(2018, 8, 30), LocalDate.now());
System.out.println(p1.getYears() + "\t" + p1.getMonths() + "\t" + p1.getDays());// 1 2 4
//工厂方法介绍
Duration threeMinutes = Duration.ofMinutes(3);
threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
Period tenDays = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
}

Temporal接口

LocalDate、LocalTime、LocalDateTime、Instant类都实现了 Temporal 接口,有很多通用的处理日期和时间的方法,比如plus(), minus(), with()

    @Test
public void test4() {
LocalDate ld = LocalDate.of(2019, 10, 7);
//修改时间对象的某个属性值,返回一个新的对象
LocalDate ld2 = ld.withDayOfYear(365);//2019-12-31
LocalDate ld3 = ld.withDayOfMonth(18);//2019-10-18
LocalDate ld4 = ld.with(ChronoField.MONTH_OF_YEAR, 8);//2019-08-07
//对时间对象进行加减运算
LocalDate ld5 = ld.plusWeeks(2L);//2019-10-21
LocalDate ld6 = ld.minusYears(9L);//2010-10-07
LocalDate ld7 = ld.plus(Period.ofMonths(2));//2019-12-07
LocalDate ld8 = ld.plus(2L, ChronoUnit.MONTHS);//2019-12-07 LocalTime lt = LocalTime.parse("10:10:10.888");
LocalTime lt1 = lt.plus(Duration.ofHours(2L));//12:10:10.888
LocalTime lt2 = lt.plus(120L, ChronoUnit.MINUTES);//12:10:10.888
}

TemporalAdjuster接口

TemporalAdjuster 类提供了更多对日期定制化操作的功能, 诸如将日期调整到下个工作日、本月的最后的一天、今年的第一天等等。

    @Test
public void test5() {
LocalDate ld = LocalDate.of(2019, 10, 7);
LocalDate ld1 = ld.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));//2019-10-11
LocalDate ld2 = ld.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));//2019-10-07
LocalDate ld3 = ld.with(TemporalAdjusters.firstDayOfNextMonth());//2019-11-01 //自定义TemporalAdjuster, 来计算下一个工作日所在的日期
LocalDate ld4 = LocalDate.of(2019, 10, 11).with(temporal -> {
DayOfWeek now = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
long dayToAdd = now.equals(DayOfWeek.FRIDAY) ? 3L : now.equals(DayOfWeek.SATURDAY) ? 2L : 1L;
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});//2019-10-14
//对于经常复用的相同操作,可以将逻辑封装一个类中
TemporalAdjuster temporalAdjuster = TemporalAdjusters.ofDateAdjuster(temporal -> {
DayOfWeek now = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
long dayToAdd = now.equals(DayOfWeek.FRIDAY) ? 3L : now.equals(DayOfWeek.SATURDAY) ? 2L : 1L;
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});
}

DateTimeFormatter

DateTimeFormatter 用于进行可定制的日期时间格式化, 功能相当于以前的 SimpleDateFormat 类

    @Test
public void test6() {
//日期转字符串
LocalDate ld = LocalDate.of(2019, 10, 7);
String s1 = ld.format(DateTimeFormatter.BASIC_ISO_DATE);//
String s2 = ld.format(DateTimeFormatter.ISO_LOCAL_DATE);//2019-10-07
//字符串转日期
LocalDateTime ld1 = LocalDateTime.parse("2019-10-07 22:22:22.555", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
}

ZoneId时区

ZoneId 是老版本的 TimeZone 的替代品, ZonedDateTime 代表了相对于指定时区的时间点

    @Test
public void test7() {
//LocalDate、LocalDateTime、Instant 转 ZonedDateTime
ZonedDateTime zdt1 = LocalDate.of(2019, 10, 7).atStartOfDay(ZoneId.systemDefault());
ZonedDateTime zdt2 = LocalDateTime.of(2019, 10, 7, 15, 55, 55, 888).atZone(ZoneId.of("Asia/Shanghai"));
ZonedDateTime zdt3 = Instant.now().atZone(ZoneId.of("Asia/Yerevan")); //Instant转LocalDateTime
LocalDateTime ldt1 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
//下面的两个栗子介绍了ZoneOffset,他是利用和 UTC/格林尼治时间的固定偏差计算时区,但不推荐使用,因为ZoneOffset并未考虑任何夏令时的影响
LocalDateTime ldt2 = LocalDateTime.ofInstant(Instant.now(), ZoneOffset.of("+8"));
//LocalDateTime转Instant
Instant instant = LocalDateTime.of(2019, 10, 7, 15, 55, 55).toInstant(ZoneOffset.of("+4"));
}

参考资料

Java8 实战

SimpleDateFormat线程不安全及解决办法

Java进阶(七)正确理解Thread Local的原理与适用场景

作者:张小凡
出处:https://www.cnblogs.com/qingshanli/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】。

Java8系列 (六) 新的日期和时间API的更多相关文章

  1. 《Java 8 in Action》Chapter 12:新的日期和时间API

    在Java 1.0中,对日期和时间的支持只能依赖java.util.Date类.同时这个类还有两个很大的缺点:年份的起始选择是1900年,月份的起始从0开始. 在Java 1.1中,Date类中的很多 ...

  2. java 8中新的日期和时间API

    java 8中新的日期和时间API 使用LocalDate和LocalTime LocalDate的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息.另外,它也不附带任何与时区相关的信 ...

  3. Java 8 (11) 新的日期和时间API

    在Java 1.0中,对日期和时间的支持只能依赖java.util.Date类.这个类只能以毫秒的精度表示时间.这个类还有很多糟糕的问题,比如年份的起始选择是1900年,月份的起始从0开始.这意味着你 ...

  4. Java8 新的日期和时间API(笔记)

    LocalDate LocalTime Instant duration以及Period 使用LocalDate和LocalTime //2017-03-20 LocalDate date = Loc ...

  5. Java8新特性--日期和时间API

    如何正确处理时间 现实生活的世界里,时间是不断向前的,如果向前追溯时间的起点,可能是宇宙出生时,又或是是宇宙出现之前, 但肯定是我们目前无法找到的,我们不知道现在距离时间原点的精确距离.所以我们要表示 ...

  6. Java8 日期与时间 API

    在 Java 中,想处理日期和时间时,通常都会选用 java.util.Date 这个类进行处理.不过不知道是设计者在当时没想好还是其它原因,在 Java 1.0 中引入的这个类,大部分的 API 在 ...

  7. 详解Java8特性之新的日期时间 API

    详解Java8特性之新的日期时间 API http://blog.csdn.net/timheath/article/details/71326329 Java8中时间日期库的20个常用使用示例 ht ...

  8. 详解Java8的日期和时间API

    详解Java8的日期和时间API 在JDK1.0的时候,Java引入了java.util.Date来处理日期和时间:在JDK1.1的时候又引入了功能更强大的java.util.Calendar,但是C ...

  9. Java8 ,LocalDate,LocalDateTime处理日期和时间工具类,

    Java8 ,LocalDate,LocalDateTime处理日期和时间工具类 1.获取今天的日期 2.在Java 8 中获取年.月.日信息 3.在Java 8 中处理特定日期 4.在Java 8 ...

随机推荐

  1. phaser学习总结之Text对象详解

    前言 在phaser学习总结之phaser入门教程中,我们已经入门了phaser,对phaser也有所了解但是我们并没有对phaser中的每个对象的属性和方法进行详解,本章将对phaser中的Text ...

  2. 【原创】(六)Linux内存管理 - zoned page frame allocator - 1

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  3. maven突然报大量package does not exist(包不存在)问题

    遇到个问题,不知道原因,虽然已解决,但是扔不知道为什么,希望有大神帮忙解答下~~~不胜感激~~~ 国庆假期回来后,小伙伴发布测试服务器版本忽然发现报错,我咨询IT管理组近期并没有对服务器配置和权限做调 ...

  4. mac下安装rabbitmq

    使用homebrew安装rabbitmq,命令如下: brew install rabbitmq 安装的位置如下/usr/local/Cellar/rabbitmq/3.7.18 进入到sbin目录下 ...

  5. spring boot通过Spring Data Redis集成redis

    在spring boot中,默认集成的redis是Spring Data Redis,Spring Data Redis针对redis提供了非常方便的操作模版RedisTemplate idea中新建 ...

  6. Cocos Creator实现左右跳游戏,提供完整游戏代码工程

    ​1. 玩法说明 游戏开始后,点击屏幕左右两侧,机器人朝左上方或右上方跳一步,如果下一步有石块,成功得1分,否则游戏结束. 2. 模块介绍 游戏场景分为2个:主页场景(home).游戏场景(game) ...

  7. java23种设计模式(一)工厂方法模式

    在说工厂方法模式之前,先了解一下简单工厂模式.工厂方法模式其实是在简单工厂上面做了一些增强. 简单工厂模式:有一个专门的类来生产其他类的实例,生产的这些实例有一个共同父类.这个跟我们的多态有一点像. ...

  8. 利用 turtle库绘制简单图形

    turtle库是python的基础绘图库,这个库被介绍为一个最常用的用来介绍编程知识的方法库,其主要是用于程序设计入门,是标准库之一,利用turtle可以制作很多复杂的绘图. turtle名称含义为“ ...

  9. SQL 存储过程示例讲解

    create proc score_result ) --参数 as declare --定义变量@courseNo int, @testTime1 datetime, @avg int begin ...

  10. Uber Go 语言编程规范

    目录 Uber Go 语言编程规范 1. 介绍 2. 编程指南 3. 性能相关 4. 编程风格 5. 编程模式(Patterns) 6. 总结 Uber Go 语言编程规范 相信很多人前两天都看到 U ...