关于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,就不用考虑线程安全的问题。