Java 中SimpleDateFormat 错误用法及改正

简介: Java 中SimpleDateFormat 错误用法及改正

开发 Java 项目时经常操作时间、日期与字符串的互相转换,最常见简单的方式是使用 SimpleDateFormat,想必大家对它不陌生。 虽然它简单易用,如果没有正确使用,在一般环境下使用通常不会出错,但在高并发(Highly Concurrent)的环境下就可能会出现异常。

44.png

我们都知道在程序中应尽量少使用 ,因为若频繁实例化,则需要花费较多的成本,因此我们尽可能共用同一个实例。 假设有一个转换日期时间的代码如下:

public class DateUtil {
    private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    public static String format(Date date) {
        return simpleDateFormat.format(date);
    }
}

不幸的是, 就是最典型的错误用法。 官方文件提到:共用 SimpleDateFormat


Date formats are not synchronized. It is recommended to create

separate format instances for each thread. If multiple threads access

a format concurrently, it must be synchronized externally.


从 SimpleDateFormat 的源代码中也可以看到它是有状态的,而且其中 calendar 被宣告为成员变量,因此呼叫, 等 method 时会多次访问此 calendar。 在高并发环境下,将会造成资源竞争,结果值就会不符预期,甚至抛出异常。


幸运的是,已有许多解决方案:


正确用法 1. 每次都 new

public class DateUtil {
    public static String format(Date date) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return simpleDateFormat.format(date);
    }
}

这是最简单的做法,只要每次都宣告区域变量就可以了,区域变量是 thread-safe。 若专案对于效能要求不高,也许可以考虑这个解法,或直到出现效能问题时再考虑其他方法。 毕竟至少这个做法能正确运作,而且简单的作法往往是较好的。


正确用法 2. 加锁

public class DateUtil {
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    synchronized public static String format(Date date) {
        return simpleDateFormat.format(date);
    }
}

首先宣告 SimpleDateFormat field,避免重复 new 而造成性能问题。 再加上关键字 就能确保同一时刻只有一个 thread 能执行 (mutual exclusion)。 虽然这个方式不会出错,但可能降低并发度。synchronizedformat


正确用法 3. 使用 ThreadLocal 容器

ThreadLocal 容器是一种让程序达到 thread-safety 的手段,它相当于给每个 thread 都开了一个独立的存储空间,既然 thread 之间互相隔离,自然解决了 race condition 的问题,也让 thread 能重复使用 SimpleDateFormat 实例。 代码如下:

public class DateUtil {
    // 可以把 ThreadLocal<SimpleDateFormat> 視為一個全域 Map<Thread, SimpleDateFormat>,key 就是 current thread
    // 意義上相當於 currentThread 專屬、獨立的 cache。
    private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<>();
    private static SimpleDateFormat getDateFormat() {
        // currentThread 從自己的 ThreadLocalMap 取得 SimpleDateFormat。
        // 如果是 null,則建立 SimpleDateFormat 並放入自己的 ThreadLocalMap 中。
        SimpleDateFormat dateFormat = local.get();
        if (dateFormat == null) {
            dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            local.set(dateFormat);
        }
        return dateFormat;
    }
    public static String format(Date date) {
        return getDateFormat().format(date);
    }
}

举例来说,如果thread pool有10个thread,程序就会建立10个SimpleDateFormat实例,这些thread们在每次的任务中重复使用各自的SimpleDateFormat。 但要注意一点,该 thread 能够重复被使用(例如 server 在处理完一次 request 后,thread 会再回到 thread pool 待命),否则效果会和方法1差不多。 这个方法的缺点是程序会变得较复杂。


正确用法4. 改用 DateTimeFormatter(推荐)

虽然有点文不对题,毕竟这个问题困扰很多人许久了,因此在 Java 8 版本后官方就提供了 对象用来代替 。 就像官方文件中说的:DateTimeFormatterSimpleDateFormat

DateTimeFormatter in Java 8 is immutable and thread-safe alternative to SimpleDateFormat.

简单的演示例如下:

将字符串转成 LocalDate

String dateStr = "2022/05/24";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date = LocalDate.parse(dateStr, formatter);

LocalDateTime 转成字符串

LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh:mm");
System.out.println(now.format(formatter));


相关文章
|
4月前
|
Java
Java中的equals()与==的区别与用法
【7月更文挑战第28天】
67 12
|
14天前
|
存储 安全 Java
深入理解Java中的FutureTask:用法和原理
【10月更文挑战第28天】`FutureTask` 是 Java 中 `java.util.concurrent` 包下的一个类,实现了 `RunnableFuture` 接口,支持异步计算和结果获取。它可以作为 `Runnable` 被线程执行,同时通过 `Future` 接口获取计算结果。`FutureTask` 可以基于 `Callable` 或 `Runnable` 创建,常用于多线程环境中执行耗时任务,避免阻塞主线程。任务结果可通过 `get` 方法获取,支持阻塞和非阻塞方式。内部使用 AQS 实现同步机制,确保线程安全。
|
2月前
|
安全 Java API
时间日期API(Date,SimpleDateFormat,Calendar)+java8新增日期API (LocalTime,LocalDate,LocalDateTime)
这篇文章介绍了Java中处理日期和时间的API,包括旧的日期API(Date、SimpleDateFormat、Calendar)和Java 8引入的新日期API(LocalTime、LocalDate、LocalDateTime)。文章详细解释了这些类/接口的方法和用途,并通过代码示例展示了如何使用它们。此外,还讨论了新旧API的区别,新API的不可变性和线程安全性,以及它们提供的操作日期时间的灵活性和简洁性。
|
2月前
|
Java
Java 正则表达式高级用法
Java 中的正则表达式是强大的文本处理工具,用于搜索、匹配、替换和分割字符串。`java.util.regex` 包提供了 `Pattern` 和 `Matcher` 类来高效处理正则表达式。本文介绍了高级用法,包括使用 `Pattern` 和 `Matcher` 进行匹配、断言(如正向和负向前瞻/后顾)、捕获组与命名组、替换操作、分割字符串、修饰符(如忽略大小写和多行模式)及 Unicode 支持。通过这些功能,可以高效地处理复杂文本数据。
|
2月前
|
存储 Java 数据处理
Java 数组的高级用法
在 Java 中,数组不仅可以存储同类型的数据,还支持多种高级用法,如多维数组(常用于矩阵)、动态创建数组、克隆数组、使用 `java.util.Arrays` 进行排序和搜索、与集合相互转换、增强 for 循环遍历、匿名数组传递以及利用 `Arrays.equals()` 比较数组内容。这些技巧能提升代码的灵活性和可读性,适用于更复杂的数据处理场景。
|
2月前
|
安全 Java
Java switch case隐藏用法
在 Java 中,`switch` 语句是一种多分支选择结构,常用于根据变量值执行不同代码块。除基本用法外,它还有多种进阶技巧,如使用字符串(Java 7 开始支持)、多个 `case` 共享代码块、不使用 `break` 实现 “fall-through”、使用枚举类型、使用表达式(Java 12 及以上)、组合条件以及使用标签等。这些技巧使代码更加简洁、清晰且高效。
|
3月前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
42 2
|
3月前
|
安全 Java
java系列知识之~SimpleDateFormat日期格式化
这篇文章介绍了Java中`SimpleDateFormat`类的使用,包括如何创建实例、格式化日期对象为字符串、解析字符串为日期对象,并提供了常见日期模式和使用示例,同时指出了`SimpleDateFormat`不是线程安全及其它一些注意事项。
|
4月前
|
Java
java中return,break以及continue的用法
java中return,break以及continue的用法
47 10
|
4月前
|
Java 编译器 数据库连接
Java面试题:什么是Java中的注解以及如何自定义注解?举例说明注解的经典用法
Java面试题:什么是Java中的注解以及如何自定义注解?举例说明注解的经典用法
85 0