你使用过SimpleDateFormat么?

本文深入探讨了SimpleDateFormat在多线程环境下的线程安全问题,分析了其内部机制导致的问题根源,并提供了四种解决方案,包括每次使用时创建新实例、使用synchronized关键字、ThreadLocal以及基于JDK1.8的DateTimeFormatter。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 问题场景复现
    开发中我们经常会用到时间相关的类,想必大家对SimpleDateFormat并不陌生。但是SimpleDateFormat并不是一个线程安全的类。在多线程的情况下会出现异常(遇到坑的小伙伴深有感触吧-.-)。下面我们来分析它为什么不安全?
    看下《阿里巴巴java开发手册》对于SimpleDateFormat是怎么看待的
    在这里插入图片描述
    问题场景复现
    一般我们使用SimpleDateFormat的时候会把它定义为一个静态的变量,避免频繁创建它的实例对象。
 private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static String formatDate(Date date) throws ParseException {
        return sdf.format(date);
    }

    public static Date parse(String strDate) throws ParseException {
        return sdf.parse(strDate);
    }

    public static void main(String[] args) throws InterruptedException, ParseException {

        System.out.println(sdf.format(new Date()));

    }

单线程下没什么问题,但是运用到多线程下呢?

public static void main(String[] args) throws InterruptedException, ParseException {

    ExecutorService service = Executors.newFixedThreadPool(100);

    for (int i = 0; i < 20; i++) {
        service.execute(() -> {
            for (int j = 0; j < 10; j++) {
                try {
                    System.out.println(parse("2018-01-02 09:45:59"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    // 等待上述的线程执行完
    service.shutdown();
    service.awaitTermination(1, TimeUnit.DAYS);
}

输出结果:
在这里插入图片描述

  • 多线程不安全原因
    我们把SimpleDateFormat 定义为静态变量,那么多线程下SimpleDateFormat的实例就会被多个线程共享,比如B线程可能就会读取到A线程的时间,就会出现时间差异或者直接挂了。我们看下它的源码
    在这里插入图片描述
    calendar变量是一个共享变量,可以被多个线程访问。假设A线程把时间设置成2019-04-11,A挂起,线程B执行把时间设置成2019-03-20,B挂起,A执行,这时候calendar就拿到B的值。这样就会时间不对,挂死。

  • 解决方案
    1.不用static修饰,什么时候用到什么时候创建实例。

 public static String formatDate(Date date) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }

    public static Date parse(String strDate) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.parse(strDate);
    }

2.使用synchronized

  public static String formatDate(Date date) throws ParseException {
        synchronized(sdf){
            return sdf.format(date);
        }
    }

    public static Date parse(String strDate) throws ParseException {
        synchronized(sdf){
            return sdf.parse(strDate);
        }
    }

3.ThreadLocal

  private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static Date parse(String dateStr) throws ParseException {
        return threadLocal.get().parse(dateStr);
    }

    public static String format(Date date) {
        return threadLocal.get().format(date);
    }

4.基于JDK1.8的DateTimeFormatter

package com.physicalexam.all.utils;

import java.text.ParseException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author b
 * @date 2019/4/11
 */
public class SimpleDateFormatUtil {

    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static String formatDate2(LocalDateTime date) {
        return formatter.format(date);
    }

    public static LocalDateTime parse2(String dateNow) {
        return LocalDateTime.parse(dateNow, formatter);
    }

    public static void main(String[] args) throws InterruptedException, ParseException {

        ExecutorService service = Executors.newFixedThreadPool(100);
        System.out.println(LocalDateTime.now());
        // 20个线程
        for (int i = 0; i < 20; i++) {
            service.execute(() -> {
                for (int j = 0; j < 10; j++) {
                    try {
                        System.out.println(parse2(formatDate2(LocalDateTime.now())));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        // 等待上述的线程执行完
        service.shutdown();
        service.awaitTermination(1, TimeUnit.DAYS);


    }
}


你会用了么?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值