java线程不安全类 SimpleDateFormat

不安全在什么地方?

前段时间在做系统数据清洗过程中,因为用到多线程及simpeldateformat,一开始没注意,遇到了线程安全问题,就在此描述解决办法。

// Called from Format after creating a FieldDelegate
    private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);

    ....                                    
}

private void subFormat(int patternCharIndex, int count,
                           FieldDelegate delegate, StringBuffer buffer,
                           boolean useDateFormatSymbols)
    {
        int     maxIntCount = Integer.MAX_VALUE;
        String  current = null;
        int     beginOffset = buffer.length();

        int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
        int value;
        if (field == CalendarBuilder.WEEK_YEAR) {
            if (calendar.isWeekDateSupported()) {
                value = calendar.getWeekYear();//取值
            } else {
                // use calendar year 'y' instead
                patternCharIndex = PATTERN_YEAR;
                field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
                value = calendar.get(field);
            }

        ...

可以看到在format代码中,将要被格式化的date设置到calendar实例中,这个实例是simpledateforamt的一个局部变量。

将设A线程调用了format,此时将A线程的(假设是2018-01-05 00:00:00)这个时间set到calendar,线程刮起,线程B进来,调用该方法(假设要格式的时间是2017-12-38 10:00:05),将该时间set到calendar。此时B线程挂起,A线程执行,通过subFormat方法获取格式化的数据,但是此时的calender里的信息是B线程的,所以这种情况,format的结果均为B线程的数据。这就是该方法线程不安全的地方。

下面是个测试方法,来体现该类线程不安全

public class Main {

    @Test
    public void testSimpleDateFormatThreadSafe() throws ParseException, InterruptedException {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        long now = System.currentTimeMillis();
        long one = 1000 * 60 * 60 * 24;
        long[] times = { now, now - one, now - one * 2, now - one * 3, now - one * 4, now - one * 5, now - one * 6,
                now - one * 7, now - one * 8, now - one * 9, now - one * 10, now - one * 11, now - one * 12,
                now - one * 13, now - one * 14, now - one * 15, now - one * 16, now - one * 17, now - one * 18,
                now - one * 19, now - one * 20, now - one * 21, now - one * 22, now - one * 23, now - one * 24,
                now - one * 25, now - one * 26, now - one * 27, now - one * 28, now - one * 29 };

        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 30; i++) {
            pool.execute(new Work(sdf, times[i]));
            // System.out.println(sdf.format(new Date(times[i])));//07
        }

         Thread.sleep(10000);

    }

    public class Work implements Runnable {

        SimpleDateFormat sdf;
        long date;

        public Work(SimpleDateFormat sdf, long date) {
            this.sdf = sdf;
            this.date = date;
        }

        @Override
        public void run() {
            System.out.println(sdf.format(new Date(date)));
        }

    }

}

如何让它变的安全呢?

可以使用java的ThreadLocal类,该类会为每个线程实例化一个类,这样多线程之间就没有竞争对象了。这个类的原理很简单,其内部维护了一个map,key是线程的名字,value就是实例化对象。

ThreadLocal<SimpleDateFormat> safe = new ThreadLocal() {

            @Override
            protected Object initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd");  
            }

        };


pool.execute(new Work(safe.get(),times[i]));//线程安全

通过safe直接get得到的simpledateformat的对象,就是为每个线程独立创建的。这种做法在我们熟悉的tomcat为每个单独的访问保存其专有信息,就是使用的这种方法。

如果觉得为每个线程都实例化对象开销比较大或者造成gc,也可以维护一个对象池。

实例代码:https://github.com/yangzhenkun/learn/blob/master/src/main/java/com/yasin/threadsafe/Main.java

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值