写在前面
我们回顾一下,在我们Java8之前,我们想要操作时间,是使用传统的日期时间API即Date这个类,而在Java8中新增了LocalTime、LocalDate和LocalDateTime,日期和时间的处理变得更加方便和容易。
为什么要有新的时间API
1、JDK1.0 版本中,我们经常用的Date方法里很多方法都弃用了,标注了Deprecated,我们如果再用就是给自己埋坑,所以有新的API还是用新的放心,万一哪天彻底弃用了,就要哭了。
2、JDK1.1 版本中出现了一个Calendar类配合Date类,可以对时间和日期做一些运算,比如获取周几或者某天的0点再或者将当前日期往前往后移动几天,都需要写好多代码,代码不简洁。
3、传统的java.util.Date和格式化类SimpleDateFormatter都是可变类,是线程不安全的,在多线程的环境下对共享变量Date进行操作时,需要自己保证线程安全,而全新的时间API新增的LocalTime、LocalDate和LocalDateTime都是fianl类不可变的是线程安全的。
传统日期时间格式的线程不安全问题
下面我们用传统的Date那套日期时间API写一个例子,我们想要使用SimpleDateFormat类来对一个时间或者日期进行格式化,并且使用多线程来同时对一个时间或日期进行格式化。
我们创建一个线程池,然后分10次去访问定义好的一个任务,都来解析某个时间或者日期。
public class TestSimpleDateFormat {
public static void main(String[] args) throws ExecutionException, InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
//定义好如下一个任务,专门用于格式化日期和时间
Callable<Date> task = new Callable<Date>() {
@Override
public Date call() throws Exception {
return sdf.parse("20210317");
}
};
//创建一个长度为10的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(pool.submit(task));
}
for (Future<Date> future:list) {
System.out.println(future.get());
}
}
}
运行结果如下,说明已经格式化报错了,也就是有线程安全问题了,所以说SimpleDateFormat类或传统的时间日期API存在多线程不安全的问题。
我们怎样才能让他变成是线程安全的呢?当然是加锁啦,下面我们试试使用ThreadLocal类对以上程序中的sdf变量上锁。
用ThreadLocal对上面程序找那个的sdf进行加锁
public class DateFormatThreadLocal {
//threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,
// 只有指定线程可以得到存储数据,大致意思就是ThreadLocal提供了线程内存储变量的能力,
// 这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。
//相当于是加了锁的变量
private static final ThreadLocal<DateFormat> sdf = new ThreadLocal<DateFormat>(){
//ThreadLocal里面有一个initialValue()方法
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
public static Date covert(String str) throws ParseException {
return sdf.get().parse(str);
}
}
再来进行测试
public class TestSimpleDateFormat {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
//
// //定义好如下一个任务,专门用于格式化日期和时间
// Callable<Date> task = new Callable<Date>() {
//
// @Override
// public Date call() throws Exception {
// return sdf.parse("20210317");
// }
// };
//定义好如下一个任务,专门用于格式化日期和时间
Callable<Date> task = new Callable<Date>() {
@Override
public Date call() throws Exception {
//用的是SimpleDateFormat是被ThreadLocal给加了锁的
return DateFormatThreadLocal.covert("20210317");
}
};
//创建一个长度为10的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(pool.submit(task));
}
for (Future<Date> future:list) {
System.out.println(future.get());
}
pool.shutdown();
}
}
运行结果正常,说明上锁可以解决多线程安全问题。
Java8中全新的日期时间API
现在我们这就要来说到说到Java8中全新的日期时间API了,完美解决多线程的线程安全问题。
public class SimpleDateFormatJava8 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
//
// //定义好如下一个任务,专门用于格式化日期和时间
// Callable<Date> task = new Callable<Date>() {
//
// @Override
// public Date call() throws Exception {
// return sdf.parse("20210317");
// }
// };
//定义好如下一个任务,专门用于格式化日期和时间
//我们传统的API泛型是Date
Callable<Date> task = new Callable<Date>() {
@Override
public Date call() throws Exception {
//用的是SimpleDateFormat是被ThreadLocal给加了锁的
return DateFormatThreadLocal.covert("20210317");
}
};
//标准的日期时间格式写法
//DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME;
//自己指定日期时间格式
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyyMMdd");
//定义好如下一个任务,专门用于格式化日期和时间
//我们新的API泛型就是LocalDate了
Callable<LocalDate> taskNew = new Callable<LocalDate>() {
@Override
public LocalDate call() throws Exception {
//用的是LocalDate了
return LocalDate.parse("20210317",df);
}
};
//创建一个长度为10的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(pool.submit(task));
}
for (Future<Date> future:list) {
System.out.println(future.get());
}
pool.shutdown();
}
}
运行结果也是正常的,不存在线程安全问题
说明我们Java 8中这套全新的日期时间API也是线程安全的。