SimpleDateFormat的线程安全

(一)引子

最近看公司同事写的日期格式化代码:

 public static  String formatDate(Date date)throws ParseException{
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(date);
 }

 就想起之前所学到的,SimpleDateFormat是线程不安全的。

此处的代码也印证了之前所学,所以同事的代码每次都新new一个SimpleDateFormat。

虽然不高明,但杜绝了安全隐患。

更甚一步,觉得自己应该详细读下其源码,理解它在哪一点上不安全。

(二)如何不安全

看format的源码,一段代码跃然纸上:

 1 private StringBuffer format(Date date, StringBuffer toAppendTo,
 2                                 FieldDelegate delegate) {
 3         // Convert input date to time field list
 4         calendar.setTime(date);
 5 
 6         boolean useDateFormatSymbols = useDateFormatSymbols();
 7 
 8         for (int i = 0; i < compiledPattern.length; ) {
 9             int tag = compiledPattern[i] >>> 8;
10             int count = compiledPattern[i++] & 0xff;
11             if (count == 255) {
12                 count = compiledPattern[i++] << 16;
13                 count |= compiledPattern[i++];
14             }
15 
16             switch (tag) {
17             case TAG_QUOTE_ASCII_CHAR:
18                 toAppendTo.append((char)count);
19                 break;
20 
21             case TAG_QUOTE_CHARS:
22                 toAppendTo.append(compiledPattern, i, count);
23                 i += count;
24                 break;
25 
26             default:
27                 subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
28                 break;
29             }
30         }
31         return toAppendTo;
32     }

第四行代码 calendar.setTime(date) 缓存了时间在成员变量calendar中,而subFormat又使用了calendar

所以多个线程调用SimpleDateFormat时,共用变量calendar,互相影响。

(三)验证下其不安全

验证代码如下——

package com.concurrent;

import java.text.ParseException;
import java.util.Date;

/**
 * Created by baimq on 2018/1/10.
 */
public class SimpleDateFormatTest {


    public static class TestSimpleDateFormatThreadSafe extends Thread {
        @Override
        public void run() {
            while(true) {
                try {
                    this.join(2000);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                try {
                    System.out.println(this.getName()+":"+new Date()+":"+ DateUtil.parse("2018-01-10 22:00:20"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public static void main(String[] args) {
        for(int i = 0; i < 3; i++){
            new TestSimpleDateFormatThreadSafe().start();
        }

    }

}

(四)如何安全

(1)每次使用时new一个SimpleDateFormat

好处是线程安全,代码简单

坏处是开销大

(2)使用theadlocal

其原理是,每次使用时复制一份SimpleDateFormat,所以不会有线程安全问题

代码如下——

 private static  ThreadLocal<SimpleDateFormat> threadLocalSDF = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static  String formatDate(Date date)throws ParseException{
        return threadLocalSDF.get().format(date);
    }

(3)在博客园里还见到其它方案

  1.使用Apache commons 里的FastDateFormat,宣称是既快又线程安全的SimpleDateFormat, 可惜它只能对日期进行format, 不能对日期串进行解析。

  2.使用Joda-Time类库来处理时间相关问题

原文地址:https://www.cnblogs.com/baimingqian/p/8260995.html