非執行緒安全類SimpleDateFormat的禁忌
阿新 • • 發佈:2019-01-31
關於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,就不用考慮執行緒安全的問題。