一、传统的时间与日期API
传统的时间与日期API最大的缺点就是线程不安全
看如下代码:
public class TestSimpleDateformat {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<Date>> futureList =new ArrayList<>();
Callable<Date> task = new MyCallable();
for (int i = 0; i < 10; i++) {
futureList.add(executorService.submit(task));
}
futureList.forEach((f)-> {
try {
System.out.println(f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
executorService.shutdown();
}
}
class MyCallable implements Callable<Date>{
private static final SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd");
@Override
public Date call() throws Exception {
return sdf.parse("2020-2-1");
}
}
我看可以看到出现会出现错误,SimpleDateFormat继承了DateFormat,在DateFormat中定义了一个protected属性的 Calendar类的对象:calendar。只是因为Calendar累的概念复杂,牵扯到时区与本地化等等,Jdk的实现中使用了成员变量来传递参数,这就造成在多线程的时候会出现错误。
传统最常用的解决办法就是使用ThreadLocal线程局部变量来解决
如下代码:
public class TestSimpleDateformat {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<Date>> futureList =new ArrayList<>();
Callable<Date> task = new MyCallable();
for (int i = 0; i < 10; i++) {
futureList.add(executorService.submit(task));
}
futureList.forEach((f)-> {
try {
System.out.println(f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
executorService.shutdown();
}
}
class MyCallable implements Callable<Date>{
private static final ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
@Override
public Date call() throws Exception {
return threadLocal.get().parse("2020-2-1");
}
}
程序输出结果正确。
二、新时间与日期API
public class TestSimpleDateformat {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<LocalDate>> futureList =new ArrayList<>();
Callable<LocalDate> task = new MyCallable();
for (int i = 0; i < 10; i++) {
futureList.add(executorService.submit(task));
}
futureList.forEach((f)-> {
try {
System.out.println(f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
executorService.shutdown();
}
}
class MyCallable implements Callable<LocalDate>{
private static final DateTimeFormatter dtf =DateTimeFormatter.ISO_LOCAL_DATE;
@Override
public LocalDate call() throws Exception {
return LocalDate.parse("2020-02-01",dtf);
}
}
程序结果:
可以看到无论有多少个线程在执行,都创建了一个新的LocalDate实例,所有线程安全。
三、本地时间与时间戳
使用LocalDate、LocalTime、LocalDateTime
LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
public class TestLocalDateTime {
/*
* 1.LocalDate 本地日期
* 2.LocalTime 本地时间
* 3.LocalDateTime 本地日期与时间
*
* 本测试是使用LocalDateTime为例,另外两个和这个一样
*/
@Test
public void test01(){
//获取当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
//指定本地时间
LocalDateTime time = LocalDateTime.of(2020, 2, 1, 15, 33, 16);
System.out.println(time);
//日期运算
//在当前的日期上加8年
LocalDateTime nowPlus = now.plusYears(8);
System.out.println(nowPlus);
//在当前的日期上减8天
LocalDateTime nowMinus = now.minusDays(8);
System.out.println(nowMinus);
//获取时间
//获取年份
System.out.println(now.getYear());
/*
*获取月份,注意得到月份有两个方法,一个是getMonth(),得到Month这个
*枚举类,你可以用这个类的getValue()方法得到月份,而getMonthValue()
*可以直接得到月份
*/
System.out.println(now.getMonthValue());
System.out.println(now.getMonth().getValue());
//得到天数
System.out.println(now.getDayOfMonth());
//得到小时
System.out.println(now.getHour());
//得到分钟
System.out.println(now.getMinute());
//得到秒
System.out.println(now.getSecond());
}
/*
* Instant:时间戳(以Unix元年:1970年1月1日00:00:00到某个时间之间的毫秒值)
* 计算机读到的时间
*/
@Test
public void test02(){
/*
* 笔者当前的时间为2020-02-01T16:01:07.753,而输出结果是2020-02-01T08:01:7.753Z
* 那为什么会出现这种情况呢?默认获取UTC时区的时间,不知道的读者可以去百度,美国就是使用
* 的UTC时区,美国与我国时差相差8个小时,那么在2020-02-01T08:01:7.753Z时间上加8个小时
* 就对于2020-02-01T16:01:07.753
*/
Instant now = Instant.now();
System.out.println(now);
//做偏移量运算检验以下,结果是完全正确的
OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
//转为毫秒值
System.out.println(now.toEpochMilli());
//结果1970-01-01T00:01:00Z
Instant instant = Instant.ofEpochSecond(60);
System.out.println(instant);
}
/*
* Duration:计算两个“时间”之间的间隔
* period:计算两个“日期”之间的间隔
*/
@Test
public void test03(){
Instant instant1 = Instant.now();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant instant2 = Instant.now();
Duration between = Duration.between(instant1, instant2);
//得到两个时间之间相差多少秒
System.out.println(between.getSeconds());
}
/*
*
* period:计算两个“日期”之间的间隔
*/
@Test
public void test04(){
LocalDate localDate1 = LocalDate.of(2020,1,1);
LocalDate localDate2 = LocalDate.now();
Period between = Period.between(localDate1, localDate2);
System.out.println(between.getMonths());
}
}
四、时间校正器
TemporalAdjuster:时间校正器。有时我们可以需要获取例如:将时间调整到“下个周末”等操作。
TemporalAdjusters:该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
public class TestTemporalAdjuster {
/*
* TemporalAdjuster:时间校正器
*/
@Test
public void test05(){
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
//把当前时间的天数改为8,如2020-02-01T16:36:46.533改为2020-02-08T16:36:46.533
LocalDateTime localDateTime = now.withDayOfMonth(8);
System.out.println(localDateTime);
//把当前时间调整为下周六
LocalDateTime with = now.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
System.out.println(with);
//自定义:下一个结婚纪念日,今天当作纪念日,再过12*30天就是下一个结婚纪念日
LocalDateTime time = now.with((t) -> {
LocalDateTime marryTime = (LocalDateTime) t;
LocalDateTime nextMarryTime = marryTime.plusDays(12*30);
return nextMarryTime;
});
System.out.println(time);
}
}
五:时间日期格式化与时区的处理
public class TestDateTimeFormatter {
/*
* DateTimeFormatter:格式化时间/日期
*/
@Test
public void test01(){
//使用默认的格式化
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
LocalDateTime now = LocalDateTime.now();
System.out.println(now.format(formatter));
//使用自定义的
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(now.format(formatter1));
LocalDateTime parse = LocalDateTime.parse(now.format(formatter1), formatter1);
System.out.println(parse);
}
/*
* java8中加入了对时区的支持,带时区的时间分别为:
* ZonedDate、ZonedTime、ZonedDateTime
* 其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式
* 例如:Asia/Shanghai等
* ZoneId:该类中包含了所有的时区信息
* getAvailableZonelds():可以获取所有时区信息
* of(id):用指定的时区信息获取ZoneId对象
*/
@Test
public void test02(){
//有多少可以支持的时区
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
availableZoneIds.forEach(System.out::println);
}
@Test
public void test03(){
//指定一个时区构建时间
LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Rangoon"));
System.out.println(now);
//构建带时区的时间
ZonedDateTime zonedDateTime = now.atZone(ZoneId.of("Asia/Rangoon"));
System.out.println(zonedDateTime);
}
}