我们回顾下,JDK1.8之前的 SimpleDateFormat 线程安全问题,下图是阿里巴巴java 开发规范中描述 SimpleDateFormat 片段:
首先我们看下线程不安全部分的源码:
我们把重点放在 calendar ,这个 format 方法在执行过程中,会操作成员变量 calendar 来保存时间 calendar.setTime(date) 。
但由于在声明 SimpleDateFormat 的时候,使用的是 static 定义的,那么这个 SimpleDateFormat 就是一个共享变量,SimpleDateFormat 中的 calendar 也就可以被多个线程访问到,当多个线程同时使用相同的SimpleDateFormat 对象调用format方法时,多个线程会同时调用calendar.setTime方法,可能一个线程刚设置好time值另外的一个线程马上把设置的time值给修改了导致返回的格式化时间可能是错误的。
除了 format() 方法以外,SimpleDateFormat 的 parse 方法也有同样的问题。
SimpleDateFormat如何保证线程安全
- 避免线程之间共享一个SimpleDateFormat对象,每个线程使用时都创建一次SimpleDateFormat对象 => 创建和销毁对象的开销大
- 对使用format和parse方法的地方进行加锁 => 线程阻塞性能差
- 使用ThreadLocal保证每个线程最多只创建一次SimpleDateFormat对象 => MVP的解决方法
JDK 1.8,为我们提供了全新的日期和时间API
在使用Java程序操作数据库时,我们需要把数据库类型与Java类型映射起来。下表是数据库类型与Java新旧API的映射关系:
数据库 | 对应Java类(旧) | 对应Java类(新) |
DATETIME | java.util.Date | LocalDateTime |
DATE | java.sql.Date | LocalDate |
TIME | java.sql.Time | LocalTime |
TIMESTAMP | java.sql.Timestamp | LocalDateTime |
LocalDate(日期类):
// 01:获取当前时间
LocalDate now = LocalDate.now();
System.out.println("当前时间:" + now);
// 02:自定义当前时间
LocalDate data = LocalDate.of(1997, 02, 21);
System.out.println("自定义当前时间:" + data);
// 03:修改时间的格式
String dataWithString = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
System.out.println("当前时间的String格式:" + dataWithString);
// 04:解析当前时间
LocalDate parseTime = LocalDate.parse("1997-02-21");
System.out.println("解析当前的时间" + parseTime);
// 05:获取今天是本月的第几天
int dayOfMonth = LocalDate.now().getDayOfMonth();
System.out.println("dateOfMonth:" + dayOfMonth);
// 06:获取今天是今年的第多少天
int dateOfYear = LocalDate.now().getDayOfYear();
System.out.println("dateOfYear:" + dateOfYear);
// 07:获取年
int year = LocalDate.now().getYear();
System.out.println("year:" + year);
// 08:获取星期
DayOfWeek week = LocalDate.now().getDayOfWeek();
System.out.println(week.getValue());
// 09:获取当前时间并给它加一定的天数
LocalDate localDate = LocalDate.now().plusDays(5);
System.out.println("添加一定天数后的时间:" + localDate);
// 10:获取当前时间并给它减一定的天数
LocalDate minusDate = LocalDate.now().minusDays(9);
System.out.println("减去一定天数后的时间:" + minusDate);
// 11:计算两个日期间的天数
long dates = localDate.until(minusDate, ChronoUnit.DAYS);
System.out.println("相差的天数:" + dates);
// 12:计算两个日期间的天数的周数
long until = localDate.until(minusDate, ChronoUnit.WEEKS);
System.out.println("相差的周数:" + until);
LocalTime类(时间类):
// 01:获取带毫秒值的当前时间
LocalTime now = LocalTime.now();
System.out.println("当前时间:" + now);
// 02:获取不带毫秒值的当前时间
LocalTime nowWithNoNano = LocalTime.now().withNano(0);
System.out.println("不带毫秒值的当前时间:" + nowWithNoNano);
// 03:获取不带毫秒值不带分钟的时间
LocalTime nowWithNoSeconde = LocalTime.now().withNano(0).withSecond(0);
System.out.println("不带毫秒不带分钟的时间:" + nowWithNoSeconde);
// 04:获取不带毫秒值不带分钟的时间还可以这样做(格式化处理)
String nowTime = LocalTime.now().format(DateTimeFormatter.ofPattern("hh:mm"));
System.out.println("nowTime:" + nowTime);
// 05:获取制定的时间
LocalTime localTime = LocalTime.parse("06:00");
System.out.println("指定时间:" + localTime);
// 06:获取指定时间还可以这样做
LocalTime localTime1 = LocalTime.of(6,00);
System.out.println("localTime1:" + localTime1);
LocalDateTime类(时间日期类):
// 01:获取当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println("now: " + now);
// 02:格式化一下当前时间
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss E"));
System.out.println("格式化处理后的时间: " + time);
// 03:使用with开头的方法可以设置对应的年月日等
LocalDateTime localDateTime = LocalDateTime.now().withYear(1997);
System.out.println("localDateTime: " + localDateTime);
// 04:使用get开头的方法可以获取对应的年月日等
int dayOfMonth = LocalDateTime.now().getDayOfMonth();
System.out.println("dayOfMonth: " + dayOfMonth);
日期计算
增加、减少年数、月数、天数等,以LocalDateTime为例
//修改LocalDate、LocalTime、LocalDateTime、Instant
LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 10,
14, 46, 56);
//增加一年
localDateTime = localDateTime.plusYears(1);
localDateTime = localDateTime.plus(1, ChronoUnit.YEARS);
//减少一个月
localDateTime = localDateTime.minusMonths(1);
localDateTime = localDateTime.minus(1, ChronoUnit.MONTHS);
//通过with修改某些值
//修改年为2020
localDateTime = localDateTime.withYear(2020);
//修改为2022
localDateTime = localDateTime.with(ChronoField.YEAR, 2022);
有些时候想知道这个月的最后一天是几号、下个周末是几号,通过提供的时间和日期API可以很快得到答案
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = localDate.with(TemporalAdjusters.firstDayOfYear());
格式化时间
DateTimeFormatter默认提供了多种格式化方式,如果默认提供的不能满足要求,可以通过DateTimeFormatter的ofPattern方法创建自定义格式化方式
LocalDate localDate = LocalDate.of(2019, 9, 10);
String s1 = localDate.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
//自定义格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String s3 = localDate.format(dateTimeFormatter);
解析时间
LocalDate localDate1 = LocalDate.parse("20190910", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate localDate2 = LocalDate.parse("2019-09-10", DateTimeFormatter.ISO_LOCAL_DATE);
DateTimeFormatter底层原理
通过final修饰类,不可被继承,final修饰变量,做成了不可变类,因为一旦分配了一个对象的某个引用,它就不能指向另一个对象的引用。