目录

  • 写在前面
  • 为什么要有新的时间API
  • 传统日期时间格式的线程不安全问题
  • Java8中全新的日期时间API



写在前面

我们回顾一下,在我们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存在多线程不安全的问题。

java多线程日志突然不打印了_java多线程日志突然不打印了

我们怎样才能让他变成是线程安全的呢?当然是加锁啦,下面我们试试使用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();
    }
}

运行结果正常,说明上锁可以解决多线程安全问题。

java多线程日志突然不打印了_java多线程日志突然不打印了_02

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多线程日志突然不打印了_API_03


说明我们Java 8中这套全新的日期时间API也是线程安全的。