1. 程式人生 > >非執行緒安全類SimpleDateFormat的禁忌

非執行緒安全類SimpleDateFormat的禁忌

關於SimpleDateFormat的用法其實很簡單,或許這個標題你會不以為然,下面通過例子來說明一個現象。

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;

public class TestSimpleDateFormat {

    public static SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
    /**
     * @param args
     */
    public static void main(String[] args) {
        for (int i = 0; i <10; i++) {
            TestTread  testTread = new TestTread();
            testTread.start();
        }
    }
}
class TestTread extends Thread{
    private int num=0;

    private String formatNumber(int n){
        String s = "0"+n;
        return s.substring(s.length()-2);
    }

    public void run() {
        while(num<5){
            Random r = new Random();
            int k = r.nextInt(100);

            Calendar c = Calendar.getInstance();
            c.add(Calendar.DATE, k);
            try {
                String s = c.get(Calendar.YEAR) + "-"
                        + formatNumber((c.get(Calendar.MONTH) + 1)) + "-"
                        + formatNumber(c.get(Calendar.DAY_OF_MONTH));
                Date date = TestSimpleDateFormat.sdf1.parse(s);
                String d1 = new Timestamp(date.getTime()).toString();
                d1 = d1.substring(0,d1.indexOf(" "));

                if(!s.equals(d1)){
                    System.out.println(s+"     "+d1);
                }
                Thread.sleep(10);
            } catch (Exception e) {
            }
            num++;
        }
    }
}
輸出了錯誤的結果如下:
2017-03-20     2018-10-21
2017-02-21     2220-10-21
2017-01-23     2220-01-23
2017-03-18     1406-02-01
2017-03-02     1406-02-01
2017-03-01     0001-03-01
2017-01-07     0001-12-12
2017-02-12     0001-12-12
2017-02-28     2017-04-04
2017-01-11     2017-02-11
2017-03-28     2017-03-01
2017-01-31     1970-01-31
2017-02-19     1970-02-19
2017-02-14     2220-02-14

每次執行結果都不會一樣

通過上面執行緒例子,發現了這個BUG,不是SimpleDateFormat本身的問題,而是用法
官方http://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html
找到Synchronization對應的位置,有下面一段描述
Date formats are not synchronized.If multiple threads access a format concurrently, it must be synchronized externally.
大概意思,“日期格式化不是同步的,多個執行緒使用一個格式化,需要進行同步使用”

要解決上述問題,最簡單的辦法,就是在需要使用SimpleDateFormat的地方都建立一個新的例項
例子中

Date date = TestSimpleDateFormat.sdf1.parse(s);
改成
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse(s);

或者新建一個方法,加上同步

在類TestSimpleDateFormat中增加方法
public synchronized static Date dateFormat(String s){
    return TestSimpleDateFormat.sdf1.parse(s);
}

Date date = TestSimpleDateFormat.sdf1.parse(s);
改成
Date date = TestSimpleDateFormat.dateFormat(s);

這裡只是SimpleDateFormat 為例說明非執行緒安全類的使用
我們常見的還有DecimalFormat也是非執行緒安全的,最後的忠告:
“非執行緒安全類例項不能作為全域性的靜態變數,使用前請先查詢API”

在Servlet中應避免使用例項變數,如果應用程式設計無法避免使用例項變數,那麼使用同步來保護要使用的例項變數。

struts1時,不推薦建立成員變數,因為action是單例的,如果建立了成員變數,就會存線上程不安全的隱患,而struts2是每一次請求都會建立一個action,就不用考慮執行緒安全的問題。