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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值