Java 中日期格式化的潜在问题

一、Bug 场景

在一个电商系统中,需要将用户下单时间以特定格式展示给用户,同时在后台也会基于这个格式化后的时间进行一些数据统计和分析。开发人员使用 SimpleDateFormat 类对日期进行格式化操作。

二、代码示例

java 复制代码
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateFormatBug {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy - MM - dd HH:mm:ss");

    public static void main(String[] args) {
        // 模拟获取到的订单时间字符串
        String orderTimeStr = "2023 - 10 - 15 14:30:00";

        try {
            Date orderDate = dateFormat.parse(orderTimeStr);
            System.out.println("解析后的日期: " + dateFormat.format(orderDate));
        } catch (ParseException e) {
            System.out.println("日期解析错误: " + e.getMessage());
        }


        // 多线程环境下测试
        Thread thread1 = new Thread(() -> {
            try {
                Date date1 = dateFormat.parse("2023 - 10 - 16 10:00:00");
                System.out.println("线程 1 解析后的日期: " + dateFormat.format(date1));
            } catch (ParseException e) {
                System.out.println("线程 1 日期解析错误: " + e.getMessage());
            }
        });

        Thread thread2 = new Thread(() -> {
            try {
                Date date2 = dateFormat.parse("2023 - 10 - 17 15:30:00");
                System.out.println("线程 2 解析后的日期: " + dateFormat.format(date2));
            } catch (ParseException e) {
                System.out.println("线程 2 日期解析错误: " + e.getMessage());
            }
        });

        thread1.start();
        thread2.start();
    }
}

三、问题描述

  1. 单线程情况 :乍一看,代码在单线程环境下运行良好,能正确地将字符串解析为 Date 对象并格式化输出。例如,上述代码在单线程执行 main 方法时,输出结果符合预期:
plaintext 复制代码
解析后的日期: 2023 - 10 - 15 14:30:00
  1. 多线程情况 :然而,在多线程环境下,SimpleDateFormat 不是线程安全的。当多个线程同时使用同一个 SimpleDateFormat 实例进行日期解析和格式化操作时,可能会出现不可预测的结果,比如抛出 ParseException 或者得到错误的格式化日期。在上述代码中启动 thread1thread2 后,可能会出现以下错误输出:
plaintext 复制代码
线程 1 日期解析错误: Unparseable date: "2023 - 10 - 16 10:00:00"
线程 2 日期解析错误: Unparseable date: "2023 - 10 - 17 15:30:00"

这是因为 SimpleDateFormat 内部维护了一些状态信息,如日历对象等,多个线程并发访问和修改这些状态时会相互干扰,导致解析或格式化失败。

四、解决方案

  1. 线程局部变量(ThreadLocal) :可以使用 ThreadLocal 为每个线程创建独立的 SimpleDateFormat 实例,避免多线程之间的状态干扰。
java 复制代码
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateFormatBugFixed {
    private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy - MM - dd HH:mm:ss"));

    public static void main(String[] args) {
        // 模拟获取到的订单时间字符串
        String orderTimeStr = "2023 - 10 - 15 14:30:00";

        try {
            Date orderDate = dateFormatThreadLocal.get().parse(orderTimeStr);
            System.out.println("解析后的日期: " + dateFormatThreadLocal.get().format(orderDate));
        } catch (ParseException e) {
            System.out.println("日期解析错误: " + e.getMessage());
        }


        // 多线程环境下测试
        Thread thread1 = new Thread(() -> {
            try {
                Date date1 = dateFormatThreadLocal.get().parse("2023 - 10 - 16 10:00:00");
                System.out.println("线程 1 解析后的日期: " + dateFormatThreadLocal.get().format(date1));
            } catch (ParseException e) {
                System.out.println("线程 1 日期解析错误: " + e.getMessage());
            }
        });

        Thread thread2 = new Thread(() -> {
            try {
                Date date2 = dateFormatThreadLocal.get().parse("2023 - 10 - 17 15:30:00");
                System.out.println("线程 2 解析后的日期: " + dateFormatThreadLocal.get().format(date2));
            } catch (ParseException e) {
                System.out.println("线程 2 日期解析错误: " + e.getMessage());
            }
        });

        thread1.start();
        thread2.start();
    }
}
  1. 使用 Java 8 的 DateTimeFormatter :Java 8 引入的 DateTimeFormatter 是线程安全的。可以使用它来替代 SimpleDateFormat
java 复制代码
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

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

    public static void main(String[] args) {
        // 模拟获取到的订单时间字符串
        String orderTimeStr = "2023 - 10 - 15 14:30:00";

        LocalDateTime orderDateTime = LocalDateTime.parse(orderTimeStr, formatter);
        System.out.println("解析后的日期: " + orderDateTime.format(formatter));


        // 多线程环境下测试
        Thread thread1 = new Thread(() -> {
            LocalDateTime date1 = LocalDateTime.parse("2023 - 10 - 16 10:00:00", formatter);
            System.out.println("线程 1 解析后的日期: " + date1.format(formatter));
        });

        Thread thread2 = new Thread(() -> {
            LocalDateTime date2 = LocalDateTime.parse("2023 - 10 - 17 15:30:00", formatter);
            System.out.println("线程 2 解析后的日期: " + date2.format(formatter));
        });

        thread1.start();
        thread2.start();
    }
}
相关推荐
qfljg43 分钟前
OpenRewrite 实战指南
java
Sylvia33.1 小时前
LOL实时数据接入深度解析:从WebSocket到完整数据模型
java·网络·python·websocket·网络协议
2601_962071576 小时前
【Java报错已解决】org.springframework.beans.factory.BeanCreationException
java·开发语言
zww89491117 小时前
酒馆预约系统开发实战:从需求分析到上线全流程指南
java·eclipse
zww89491119 小时前
匿名树洞系统开发实战:从需求分析到部署指南
java·eclipse
黑马程序员毕设10 小时前
基于Java的医院药品管理系统的优化设计与实现
java·开发语言·spring boot·小程序·架构·课程设计·毕设
木井巳11 小时前
【BFS/DFS 解决 FloodFill 算法】太平洋大西洋水流问题
java·算法·leetcode·深度优先·广度优先·宽度优先·推荐算法
白山编程大哥11 小时前
Java 集合算法:从排序、查找到底层原理的实战指南
java·python·算法
devpotato11 小时前
缓存与数据库更新顺序不一致问题
java·数据库·redis
xcl092511 小时前
酒馆预约系统开发实战:从需求分析到上线全流程指南
java·spring boot·需求分析