今天在跑压力测试的过程中,一个看似不可能出错的地方居然报错了,起因在于我们定义的DateUtil工具类,用于将日期进行合理的format以及parse,出现了多线程问题(在单线程时不会出错,只有压测过程中会出现错误)。
代码上分析,原来编写的DateUtil简直是漏洞百出,首先将SimpleDateFormat定义为static变量,这表明在JVM中仅存在一份:
private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(YMD_HYPHEN_PATTERN);
在该工具类方法中,有多个方法可能会修改该引用的pattern:
public static String format(Date date) { if (date == null) { return ""; } DATE_FORMAT.applyPattern(YMD_HYPHEN_PATTERN); return DATE_FORMAT.format(date); }
在不同方法中,都会对该静态变量引用进行修改,这样即使SimpleDateFormat为线程安全的,都会产生多线程问题,更何况SimpleDateFormat根本不是线程安全的!
SimpleDateFormat 是 Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的,在多线程环境下调用 format() 和 parse() 方法应该使用同步代码来避免问题。
如果将DateUtil写成下面这样:
public class DateUtil { 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); } }
表面上看是不会产生任何问题的,但是由于SimpleDateFormat的线程不安全性,一旦负载上来了,这个问题就出来了,会出现各种不同的情况,比如转换的时间不正确,线程被挂死,出现NumberFormatException等。
正是由于SimpleDateFormat对象的创建比较耗费资源,开发人员才会想要通过共享变量的方式来节省资源,但这种危险的使用方式是不提倡的,除非确定你的代码不会被多线程调用(例如单机应用或Map/Reduce这种基于进程的方式)。
解决该问题有下面几种方法:
1.方法内部创建SimpleDateFormat,不管是什么时候,将有线程安全的对象由共享变为私有局部变量都可以避免多线程问题,不过也加重了创建对象的负担,虽然随时创建SimpleDateFormat会造成一定的性能影响,而且会对GC产生一定的压力,但这并不是核心问题,只要能产生正确的结果:
public static String format(Date date) { if (date == null) { return ""; } return new SimpleDateFormat(YMD_HYPHEN_PATTERN).format(date); }
2.将SimpleDateFormat进行同步使用,在每次执行时都对其加锁,这样也会影响性能,想要调用此方法的线程就需要block,当多线程并发量比较大时会对性能产生一定影响;
public static String formatDate(Date date)throws ParseException{ synchronized(sdf){ return sdf.format(date); } }
在任何公共的地方使用该类时,都需要对SimpleDateFormat进行加锁。
3.使用ThreadLocal变量,用空间换时间,这样每个线程就会独立享有一个本地的SimpleDateFormat变量;
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); }
这样就可以保证每个线程的本地变量都是安全的,不同线程之间并不共享相同的SimpleDateFormat,从而避免了线程安全问题。
如果需要对性能比较敏感,可以采用这种方式,至少比前两种的速度要快,但是占用内存也会大一点(但也不会多么夸张)。
4.众所周知,JDK的日期API是非常逆天难用的(至少在Java8之前),可以考虑使用优秀的第三方库,例如joda-time。
对于JDK中的日期API,有很多值得吐槽的地方,例如Date中的年份是从1900年开始,月份都是从0开始...
Joda-time是一个面向Java应用程序的日期/时间库的替代选择,它可以令时间和日期值变得易于管理、操作和理解。
//获取当前时间 DateTime now = new DateTime() //获取今天的00:00:00 DateTime dateTime = new DateTime().withHourOfDay(0).withMinuteOfHour(0).withSecondOfMinute(0); //向后移动1天 DateTime tommorrow = dateTime.plusDays(1); //转换到Date Date tommorrowDate = tommorrow.toDate();
更加方便的是,直接将格式化操作内置在toString(带参数)方法中,可以传入”yyyyMMdd”将其转换至对应的字符串时间。
此外joda-time中还提供了诸如Interval,Duration这些用于计算时间范围相关的工具类。
相关推荐
缓存机制使用了一个缓存数组来存储解析和格式化的结果,但是这个缓存数组是共享的,这意味着在多线程环境中,多个线程可能会同时访问和修改这个缓存数组,导致线程安全问题。 重现SimpleDateFormat类的线程安全问题...
在给定的`TestDateFormat.java`文件中,可能包含了一个示例代码,演示了`SimpleDateFormat`在多线程环境中的潜在问题,以及如何通过上述策略之一来避免这些问题。分析这个文件可以帮助我们更好地理解线程安全问题的...
然而,在多线程环境下,`SimpleDateFormat`存在非线程安全的问题,这可能会导致程序运行时出现异常或者错误的结果。 #### 二、问题分析 ##### 1. 非线程安全的原因 `SimpleDateFormat`类内部维护了一些线程不安全...
理解`SimpleDateFormat`的线程安全问题对于开发多线程应用程序至关重要。通过使用`ThreadLocal`、每次使用时创建新实例或切换到`java.time`包中的类,可以有效地避免这类问题,提高程序的稳定性和可靠性。在实际开发...
SimpleDateFormat类不是线程安全的,这意味着在多线程环境下,如果多个线程同时访问同一个SimpleDateFormat实例,可能会导致各种问题,例如转化的时间不正确、报错、线程被挂死等等。 知识点2: 创建...
在多线程环境下,不恰当的使用`DateFormat`可能导致数据不一致、性能下降甚至程序崩溃。这篇博客将深入探讨`DateFormat`在多线程环境下的问题及其解决方案。 `DateFormat`类在设计时并未被声明为线程安全的。这意味...
在多线程环境下,如果多个线程同时使用同一个SimpleDateFormat对象,可能会导致日期时间格式化结果不正确或抛出异常。 问题的根源在于SimpleDateFormat的parse方法不是线程安全的。在多线程环境下,如果多个线程...
Java标准库中有一些类,如ArrayList、HashMap和SimpleDateFormat,并未设计为线程安全,因此在多线程环境下直接使用可能导致数据不一致或其他问题。开发者应当了解每个类的线程安全特性,以便做出正确的选择和适当地...
在多线程环境中,避免使用非线程安全的类如`SimpleDateFormat`,因为它可能导致不可预测的行为或数据不一致。为确保线程安全,可以使用`ThreadLocal`来存储线程专属的实例,或者使用Java 8及更高版本的日期时间API,...
1. 日期格式化类的线程安全问题:`SimpleDateFormat` 不是线程安全的,因此在多线程环境中,建议为每个线程创建单独的实例,或者使用 `java.time.format.DateTimeFormatter`(Java 8及更高版本)。 2. 日期时间API...
要在高并发环境下能有比较好的体验,可以使用ThreadLocal来限制SimpleDateFormat只能在线程内共享,这样就避免了多线程导致的线程安全问题。 ```java private static ThreadLocal<DateFormat> threadLocal = new ...
Java中的ThreadLocal是解决线程安全问题的一个重要工具,它提供了一种在多线程环境下为每个线程维护独立变量副本的方法,从而避免了共享状态带来的竞态条件和线程安全问题。 线程安全问题通常由全局变量和静态变量...
然而,`DateFormat`并不是线程安全的,这意味着在多线程环境中直接使用可能会导致数据不一致或者异常。这篇博客文章《Java DateFormat并发实现》探讨了这个问题以及如何在并发环境下正确地使用`DateFormat`。 `...
这类类在设计时就没有考虑线程安全,例如`SimpleDateFormat`,在1.4 JDK之前的版本中并未明确指出其线程不安全,导致许多开发者在并发场景中误用,引发错误。 在文档中清晰地记录类的线程安全性是至关重要的。如...
在多线程环境中使用SimpleDateFormat需要特别注意线程安全问题,因为SimpleDateFormat不是线程安全的,这意味着在多线程环境下,多个线程同时使用同一个SimpleDateFormat对象可能会引起数据不一致。 综上所述,这些...
在本文中,我们将详细探讨多线程编程中的一些核心概念和知识点,这些内容主要来源于“百度Java面试题 前200页精选(中)”。内容涵盖了多线程的基本概念、同步机制、线程池的使用、并发工具类的应用以及线程调度等多...
4. **线程安全问题**:`SimpleDateFormat`不是线程安全的,如果在多线程环境中未正确同步,可能会导致异常。如果你在并发环境中使用,建议每个线程都有自己独立的`SimpleDateFormat`实例,或使用`java.time` API代替...
为了解决这一问题,我们可以使用线程局部变量(ThreadLocal)或者在每次使用时实例化新的SimpleDateFormat对象,避免在多线程环境下共享同一实例。 线程池的引入是为了克服直接创建线程的弊端,如线程创建和销毁的...
这意味着在多线程环境下直接共享`DateFormat`实例可能会导致数据不一致或者异常。根据Java官方文档的建议,每个线程应该拥有自己独立的日期格式实例,或者在访问`DateFormat`时进行适当的同步控制,以避免并发问题。...