七叶笔记 » java编程 » simpledateformat线程为什么不安全

simpledateformat线程为什么不安全

原因:在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。

本教程操作环境:windows7系统、java8版、DELL G3电脑。

线程不安全验证:

/** * SimpleDateFormat线程安全测试 * 〈功能详细描述〉 * * @author 17090889 * @see [相关类/方法](可选) * @since [产品/模块版本] (可选) */public class SimpleDateFormatTest {    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000), new MyThreadFactory("SimpleDateFormatTest"));    public void test() {        while (true) {            poolExecutor.execute(new Runnable() {                @Override                public void run() {                    String dateString = simpleDateFormat.format(new Date());                    try {                        Date parseDate = simpleDateFormat.parse(dateString);                        String dateString2 = simpleDateFormat.format(parseDate);                        System.out.println(dateString.equals(dateString2));                    } catch (ParseException e) {                        e.printStackTrace();                    }                }            });        }    }

输出:

  true  false  true  true  false

出现了false,说明线程不安全

1、format方法

public StringBuffer format(Date date, StringBuffer toAppendTo,                               FieldPosition pos)    {        pos.beginIndex = pos.endIndex = 0;        return format(date, toAppendTo, pos.getFieldDelegate());    }    // Called from Format after creating a FieldDelegate    private StringBuffer format(Date date, StringBuffer toAppendTo,                                FieldDelegate delegate) {        // Convert input date to time field list        calendar.setTime(date);        boolean useDateFormatSymbols = useDateFormatSymbols();        for (int i = 0; i < compiledPattern.length; ) {            int tag = compiledPattern[i] >>> 8;            int count = compiledPattern[i++] & 0xff;            if (count == 255) {                count = compiledPattern[i++] << 16;                count |= compiledPattern[i++];            }            switch (tag) {            case TAG_QUOTE_ASCII_CHAR:                toAppendTo.append((char)count);                break;            case TAG_QUOTE_CHARS:                toAppendTo.append(compiledPattern, i, count);                i += count;                break;            default:                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);                break;            }        }        return toAppendTo;    }
 protected Calendar calendar;

可以看到,多个线程之间共享变量calendar,并修改calendar。因此在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。

此外,parse方法也是线程不安全的,parse方法实际调用的是CalenderBuilder的establish来进行解析,其方法中主要步骤不是原子操作。

解决方案:

  1、将SimpleDateFormat定义成局部变量

  2、 加一把线程同步锁:synchronized(lock)

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

/** * SimpleDateFormat线程安全测试 * 〈功能详细描述〉 * * @author 17090889 * @see [相关类/方法](可选) * @since [产品/模块版本] (可选) */public class SimpleDateFormatTest {        private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>() {        @Override        protected SimpleDateFormat initialValue() {            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");        }    };    //    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000), new MyThreadFactory("SimpleDateFormatTest"));    public void test() {        while (true) {            poolExecutor.execute(new Runnable() {                @Override                public void run() {                    SimpleDateFormat simpleDateFormat = THREAD_LOCAL.get();                    if (simpleDateFormat == null) {                        simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");                    }                    String dateString = simpleDateFormat.format(new Date());                    try {                        Date parseDate = simpleDateFormat.parse(dateString);                        String dateString2 = simpleDateFormat.format(parseDate);                        System.out.println(dateString.equals(dateString2));                    } catch (ParseException e) {                        e.printStackTrace();                    } finally {                        local.remove();                    }                }            });        }    }}

  4、使用DateTimeFormatter代替SimpleDateFormat

  DateTimeFormatter是线程安全的,默认提供了很多格式化方法,也可以通过ofPattern方法创建自定义格式化方法。

  (1)格式化日期示例:

 LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime); // 2019-11-20T15:04:29.017 DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); String strDate=localDateTime.format(dtf); System.out.println(strDate); // 2019/23/20 15:23:46

  (2)解析日期

 DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); LocalDateTime localDateTime=LocalDateTime.parse("2019/11/20 15:23:46",dtf); System.out.println(localDateTime); // 2019-11-20T15:23:46

相关文章