• 【JAVA8新的时间与日期 API】- 传统时间格式化的线程安全问题


    Java8之前的日期和时间API,存在一些问题,最重要的就是线程安全的问题。这些问题都在Java8中的日期和时间API中得到了解决,而且Java8中的日期和时间API更加强大。

    传统时间格式化的线程安全问题

    示例:

    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.concurrent.*;
    
    public class TestOldSimpleDateFormat {
        public static void main(String[] args) throws Exception {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Callable<Date> task = new Callable<Date>() {
                @Override
                public Date call() throws Exception {
                    return sdf.parse("2020-01-01");
                }
            };
            ExecutorService pool = Executors.newFixedThreadPool(10);
            List<Future<Date>> list = new ArrayList<>();
            for (int i=0;i<10;i++){
                Future<Date> future = pool.submit(task);
                list.add(future);
            }
            for (Future<Date> future : list){
                System.out.println(future.get());
            }

         pool.shutdown(); } }

    以上代码运行会报错:

    报错缘由:取部分源码解释

        /**
         * SimpleDateFormat 类的 parse 方法 部分源码
         */
        public Date parse(String source) throws ParseException
        {
            ParsePosition pos = new ParsePosition(0);
            Date result = parse(source, pos); 
            if (pos.index == 0)
                throw new ParseException("Unparseable date: "" + source + """ ,
                    pos.errorIndex);
            return result;
        }
    
    public Date parse(String text, ParsePosition pos)
        {
            // 省略上面诸多代码
            Date parsedDate;
    
            CalendarBuilder calb = new CalendarBuilder();
            try {
                //这里这个 calendar 对象是 SimpleDateFormat 类的父类 DateFormat 中的属性  : protected Calendar calendar;
                parsedDate = calb.establish(calendar).getTime();//这个 calb.establish(calendar) 方法中,这个方法中的主要步骤不是原子操作,并且会对 calendar 对象进行修改,所以在多线程环境下就会出现线程安全问题。
                // 省略下面面诸多代码
            }
            catch (IllegalArgumentException e) {
                //省略.........................
                return null;
            }
            return parsedDate;
        }
     Calendar establish(Calendar cal) {
            boolean weekDate = isSet(WEEK_YEAR)
                                && field[WEEK_YEAR] > field[YEAR];
            if (weekDate && !cal.isWeekDateSupported()) {
                // Use YEAR instead
                if (!isSet(YEAR)) {
                    set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
                }
                weekDate = false;
            }
    
            cal.clear();
            // Set the fields from the min stamp to the max stamp so that
            // the field resolution works in the Calendar.
            for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
                for (int index = 0; index <= maxFieldIndex; index++) {
                    if (field[index] == stamp) {
                        cal.set(index, field[MAX_FIELD + index]);
                        break;
                    }
                }
            }
    
            if (weekDate) {
                int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1;
                int dayOfWeek = isSet(DAY_OF_WEEK) ?
                                    field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
                if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) {
                    if (dayOfWeek >= 8) {
                        dayOfWeek--;
                        weekOfYear += dayOfWeek / 7;
                        dayOfWeek = (dayOfWeek % 7) + 1;
                    } else {
                        while (dayOfWeek <= 0) {
                            dayOfWeek += 7;
                            weekOfYear--;
                        }
                    }
                    dayOfWeek = toCalendarDayOfWeek(dayOfWeek);
                }
                cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek);
            }
            return cal;
        }

    综上,我们可以看到 SimpleDateFormat 类中的parse 方法,调用了 CalendarBuilder 的 establish(calendar) 方法,并在方法中,对 calendar 对象进行了各种判断及修改,并且这些操作都不是原子操作或同步操作,而这个calendar 对象又是 SimpleDateFormat 的父类 DateFormat 的一个实例变量,所以,在多线程同时调用SimpleDateFormat 的 parse 方法的时候,就会出现线程安全问题。

    
    

    针对以上异常,JAVA8之前的解决办法:

    1. 将 SimpleDateFormat 对象定义成局部变量。

    2. 加锁。

    3. 使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本。

    示例(加锁):

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Callable<Date> task = new Callable<Date>() {
                @Override
                public synchronized Date call() throws Exception {//加个同步,解决问题
                    return sdf.parse("2020-01-01");
    //                return DateFormatThreadLocal.convert("2020-01-01");
                }
            };
            ExecutorService pool = Executors.newFixedThreadPool(10);
            List<Future<Date>> list = new ArrayList<>();
            for (int i=0;i<10;i++){
                Future<Date> future = pool.submit(task);
                list.add(future);
            }
            for (Future<Date> future : list){
                System.out.println(future.get());
            }
            pool.shutdown();

    示例(ThreadLocal):

    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class DateFormatThreadLocal {
        private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
            protected DateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd");
            }
        };
        public static Date convert(String source) throws Exception {
            return df.get().parse(source);
        }
    }
    
    
    ////////////////////////////////////////////////////////////////
    
    public class TestOldSimpleDateFormat {
        public static void main(String[] args) throws Exception {
    //        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Callable<Date> task = new Callable<Date>() {
                @Override
                public Date call() throws Exception {
    //                return sdf.parse("2020-01-01");
                    return DateFormatThreadLocal.convert("2020-01-01");
                }
            };
            ExecutorService pool = Executors.newFixedThreadPool(10);
            List<Future<Date>> list = new ArrayList<>();
            for (int i=0;i<10;i++){
                Future<Date> future = pool.submit(task);
                list.add(future);
            }
            for (Future<Date> future : list){
                System.out.println(future.get());
            }
         pool.shutdown();
    } }

    JAVA8的解决办法:使用新的API(DateTimeFormatter  和 LocalDate )

    示例:

    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.*;
    
    public class TestOldSimpleDateFormat {
        public static void main(String[] args) throws Exception {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    
            Callable<LocalDate> task = new Callable<LocalDate>() {
                @Override
                public LocalDate call() throws Exception {
    //                return sdf.parse("2020-01-01");
                    return LocalDate.parse("2020-01-01",formatter);
                }
            };
            ExecutorService pool = Executors.newFixedThreadPool(10);
            List<Future<LocalDate>> list = new ArrayList<>();
            for (int i=0;i<10;i++){
                Future<LocalDate> future = pool.submit(task);
                list.add(future);
            }
            for (Future<LocalDate> future : list){
                System.out.println(future.get());
            }
            pool.shutdown();
        }
    }
  • 相关阅读:
    【LeetCode】N数和
    用PHP写一个双向队列
    PHP的几种遍历方法
    thinkphp中dump()方法
    【转】PHP对象在内存中的分配
    【转】PHP的执行原理/执行流程
    从头到尾彻底解析哈希表算法
    【转】TCP通信的三次握手和四次撒手的详细流程(顿悟)
    springmvc中拦截器的定义和配置
    springmvc中的异常处理方法
  • 原文地址:https://www.cnblogs.com/y3blogs/p/13172896.html
Copyright © 2020-2023  润新知