java SimpleDateFormat类浅析

本文探讨了SimpleDateFormat在多线程环境下存在的线程安全问题,详细解释了其内部实现导致的问题根源,并提供了四种解决方案,包括使用局部变量、加同步锁、ThreadLocal以及DateTimeFormatter。

1、SimpleDateFormat不是线程安全的:其中常用的format方法和parse方法会在多线程的情况下发生严重错误,这是因为在format方法中,会使用一个成员变量calendar来保存时间,

private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);

        boolean useDateFormatSymbols = useDateFormatSymbols();

        for (int i = 0; i < compiledPattern.length; ) {
            int tag = compiledPattern[i] >>> 8;
            int count = compiledPattern[i++] & 0xff;
            if (count == 255) {
                count = compiledPattern[i++] << 16;
                count |= compiledPattern[i++];
            }

            switch (tag) {
            case TAG_QUOTE_ASCII_CHAR:
                toAppendTo.append((char)count);
                break;

            case TAG_QUOTE_CHARS:
                toAppendTo.append(compiledPattern, i, count);
                i += count;
                break;

            default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                break;
            }
        }
        return toAppendTo;
    }

     由于我们在声明SimpleDateFormat的时候,使用的是static定义的。那么这个SimpleDateFormat就是一个共享变量,随之,SimpleDateFormat中的calendar也就可以被多个线程访问到。假设线程1刚刚执行完calendar.setTime把时间设置成2018-11-11,还没等执行完,线程2又执行了calendar.setTime(date)把时间改成了2018-12-12。这时候线程1继续往下执行,拿到的得到calendar.getTime()的时间就是线程2改过之后的,因此不要将SimpleDateFormat类作为一个共享变量使用。

SimpleDateFormat线程不安全测试代码:

public class SimpleDateFormateTest {
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYYY-MM-dd");
    private static ThreadFactory threadFactory = Executors.defaultThreadFactory();
    private static ExecutorService pool = new ThreadPoolExecutor(5,200,0L, TimeUnit.MILLISECONDS,new LinkedBlockingDeque<Runnable>(1024),
            threadFactory,new ThreadPoolExecutor.AbortPolicy());
    private static CountDownLatch countDownLatch = new CountDownLatch(100);

    public static void main(String[] args) throws InterruptedException {
        Set<String> datas = Collections.synchronizedSet(new HashSet<>());
        for (int i = 0; i < 100; i++) {
            //获取当前时间
            Calendar calendar = Calendar.getInstance();
            int num = i;
            pool.execute(() -> {
                //时间增加
                calendar.add(Calendar.DATE, num);
                //通过simpleDateFormat把时间转换成字符串
                String dateString = simpleDateFormat.format(calendar.getTime());
                //把字符串放入Set中
                datas.add(dateString);
                //countDown
                countDownLatch.countDown();
            });
        }
        //阻塞,直到countDown数量为0
        countDownLatch.await();
        //输出去重后的时间个数
        System.out.println(datas.size());
    }

在执行代码后,发现每次打印的datas.size的个数是变化的。

2.解决SimpleDateFormat 线程不安全问题:

      a.使用局部变量,SimpleDateFormat变成了局部变量,就不会被多个线程同时访问到了,就避免了线程安全问题。

for (int i = 0; i < 100; i++) {
   //获取当前时间
   Calendar calendar = Calendar.getInstance();
   int finalI = i;
   pool.execute(() -> {
       // SimpleDateFormat声明成局部变量
   SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
       //时间增加
       calendar.add(Calendar.DATE, finalI);
       //通过simpleDateFormat把时间转换成字符串
       String dateString = simpleDateFormat.format(calendar.getTime());
       //把字符串放入Set中
       dates.add(dateString);
       //countDown
       countDownLatch.countDown();
   });
}

        b.加同步锁,通过加锁,使多个线程排队顺序执行,避免了并发导致的线程安全问题。

for (int i = 0; i < 100; i++) {
   //获取当前时间
   Calendar calendar = Calendar.getInstance();
   int finalI = i;
   pool.execute(() -> {
       //加锁
       synchronized (simpleDateFormat) {
           //时间增加
           calendar.add(Calendar.DATE, finalI);
           //通过simpleDateFormat把时间转换成字符串
           String dateString = simpleDateFormat.format(calendar.getTime());
           //把字符串放入Set中
           dates.add(dateString);
           //countDown
           countDownLatch.countDown();
       }
   });
}

       c.使用 ThreadLocal, ThreadLocal 可以确保每个线程都可以得到单独的一个 SimpleDateFormat 的对象,那么自然也就不存在竞争问题了。

/**
* 使用ThreadLocal定义一个全局的SimpleDateFormat
*/
private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
   @Override
   protected SimpleDateFormat initialValue() {
       return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
   }
};

//用法
String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());

        d.使用DateTimeFormatter,如果是Java8应用,可以使用DateTimeFormatter代替SimpleDateFormat,这是一个线程安全的格式化工具类。就像官方文档中说的,这个类 simple beautiful strongimmutable thread-safe;

/解析日期
String dateStr= "2016年10月25日";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
LocalDate date= LocalDate.parse(dateStr, formatter);

//日期转换为字符串
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh:mm a");
String nowStr = now .format(format);
System.out.println(nowStr);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值