88°

【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 </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> CalendarBuilder();
    </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
        </span><span style="color: #008000;">//</span><span style="color: #008000;">这里这个 calendar 对象是 SimpleDateFormat 类的父类 DateFormat 中的属性  : protected Calendar calendar;</span>
        parsedDate = calb.establish(calendar).getTime();<span style="color: #008000;">//</span><span style="color: #008000;">这个 calb.establish(calendar) 方法中,这个方法中的主要步骤不是原子操作,并且会对 calendar 对象进行修改,所以在多线程环境下就会出现线程安全问题。
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> 省略下面面诸多代码</span>

} 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();
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> Set the fields from the min stamp to the max stamp so that
    </span><span style="color: #008000;">//</span><span style="color: #008000;"> the field resolution works in the Calendar.</span>
    <span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> stamp = MINIMUM_USER_STAMP; stamp &lt; nextStamp; stamp++<span style="color: #000000;">) {
        </span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> index = 0; index &lt;= maxFieldIndex; index++<span style="color: #000000;">) {
            </span><span style="color: #0000ff;">if</span> (field[index] ==<span style="color: #000000;"> stamp) {
                cal.set(index, field[MAX_FIELD </span>+<span style="color: #000000;"> index]);
                </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
            }
        }
    }

    </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (weekDate) {
        </span><span style="color: #0000ff;">int</span> weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1<span style="color: #000000;">;
        </span><span style="color: #0000ff;">int</span> dayOfWeek = isSet(DAY_OF_WEEK) ?<span style="color: #000000;">
                            field[MAX_FIELD </span>+<span style="color: #000000;"> DAY_OF_WEEK] : cal.getFirstDayOfWeek();
        </span><span style="color: #0000ff;">if</span> (!isValidDayOfWeek(dayOfWeek) &amp;&amp;<span style="color: #000000;"> cal.isLenient()) {
            </span><span style="color: #0000ff;">if</span> (dayOfWeek &gt;= 8<span style="color: #000000;">) {
                dayOfWeek</span>--<span style="color: #000000;">;
                weekOfYear </span>+= dayOfWeek / 7<span style="color: #000000;">;
                dayOfWeek </span>= (dayOfWeek % 7) + 1<span style="color: #000000;">;
            } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
                </span><span style="color: #0000ff;">while</span> (dayOfWeek &lt;= 0<span style="color: #000000;">) {
                    dayOfWeek </span>+= 7<span style="color: #000000;">;
                    weekOfYear</span>--<span style="color: #000000;">;
                }
            }
            dayOfWeek </span>=<span style="color: #000000;"> toCalendarDayOfWeek(dayOfWeek);
        }
        cal.setWeekDate(field[MAX_FIELD </span>+<span style="color: #000000;"> WEEK_YEAR], weekOfYear, dayOfWeek);
    }
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> cal;
}</span></pre> 

综上,我们可以看到 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</span>&lt;LocalDate&gt; task = <span style="color: #0000ff;">new</span> Callable&lt;LocalDate&gt;<span style="color: #000000;">() {
        @Override
        </span><span style="color: #0000ff;">public</span> LocalDate call() <span style="color: #0000ff;">throws</span><span style="color: #000000;"> 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(); } }

 

本文转载自博客园,原文链接:https://www.cnblogs.com/y3blogs/p/13172896.html

全部评论: 0

    我有话说: