Java中的SimpleDateFormat的线程安全问题详解

 更新时间:2024年01月10日 09:46:31   作者:爱coding的同学  
这篇文章主要介绍了Java中的SimpleDateFormat的线程安全问题详解,sonar 是一个代码质量管理工具,SonarQube是一个用于代码质量管理的开放平台,为项目提供可视化报告, 连续追踪项目质量演化过程,需要的朋友可以参考下

起因

sonar 是一个代码质量管理工具,SonarQube是一个用于代码质量管理的开放平台。

为项目提供可视化报告,连续追踪项目质量演化过程。

通过插件机制,Sonar可以集成不同的测试工具,代码分析工具,以及持续集成工具。

对结果进行再加工处理,通过量化的方式度量代码质量的变化。

某天它揭露了代码里这样一个级别为严重的违规。

SimpleDateFormat 源码解析

根据 sonar 的提示,SimpleDateFormat 的 format 方法可能存在线程安全的问题,那么来看下SimpleDateFormat 的源码,看看是否如此

// Called from Format after creating a FieldDelegate
    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.format() 不是线程安全的:在多线程环境下,每次调用 format 方法时,calendar 都会将时间设置为传入的时间,这样如果上一个线程的 format 方法还在执行中,肯定会导致输出的结果不正确。

如何做到线程安全

那么怎么做才能线程安全呢?

不用静态属性

每次需要格式化日期时,new 一个 DateFormat,保证线程安全,我们可以这样“优化”一下

String inputValue = game.getId() + new SimpleDateFormat( "yyyyMMdd" ).format( new Date() );

虽然线程安全了,但是这样性能就下降了,如果这段代码频繁的调用,不仅 new 一个 DateFormat 对象的代价有点大,而且频繁的 new 也会导致 gc 的压力增大

同步或者加锁

既然 format() 方法线程不安全,那就在调用时采用同步或加锁,例如

private static SimpleDateFormat df = new SimpleDateFormat( "yyyyMMdd" );// 设置日期格式
	private static final Lock LOCK = new ReentrantLock();
	@Override
	public void addGame( Game game ) {
		......
		LOCK.lock();
		try {
			String inputValue = game.getId() + df.format( new Date() );
		} finally {
			LOCK.unlock();
		}
		......
	}

DateFormat 池

加锁是解决了线程安全的问题,也不会因为 new 出太多的 DateFormat 增加 gc 的负担,但是很显然并发性能大大的降低了,如果在并发性要求较高的场合,还不如 new 一个的性能高呢,那有什么办法再优化一下吗

可以实现一个 DateFormat 池,实现如下功能

1. 设定池的大小,在池实例化的同时,实例化一批 DateFormat

2. 每个 DateFormat 都有一个状态,表明当前是空闲还是忙,初始状态为空闲

3. 当需要格式化日期时,从池里取出一个空闲的 DateFormat,同时将其标记为忙

4. 池里的 DateFormat 的 format 方法需要重写,在 format 完成后将其状态标记为空闲

用池的优势很明显:只要设定合适的池大小,就不用担心并发性能;并且也不存在增加 gc 负担的问题;当然池的实现比较复杂,而且池内部也要解决线程安全问题,可以考虑采用一些开源的池框架例如 Apache commons pool 来做

ThreadLocal<DateFormat>

在引入 ThreadLocal 之前,思考一下这个问题:如果只有一个线程,format 方法还存在线程安全问题吗? 显然不会,如果只有一个线程,那么 format 方法实际上是串行执行的,绝无可能并行执行;那么,如果在多线程环境下,给每个线程都分配一个固定的 DateFormat 来执行 format 方法,显然也不会有线程安全的问题了 ThreadLocal 就是用于这种思路的一个解决方案:为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 ThreadLocal 的使用如下

private static final String DATE_FORMAT = "yyyy-MM-dd";
private static final ThreadLocal<DateFormat> SDF  = new ThreadLocal<DateFormat>() {
    protected synchronized DateFormat initialValue() {
        return new SimpleDateFormat(
            DATE_FORMAT);
                }
        };
            ......
album.setReleaseDate(SDF.get().parse(show.getReleasedate().substring(0, 10))); // 发布日期

先看每次都 new 一个新对象和 ThreaLocal之间的差别,假设有 1 个线程要执行 n 次 format

1. new:n 个实例

2. ThreadLocal:1 个实例.但是,如果是 n 个线程,每个线程只执行 1 次 format 呢?

 new:n 个实例

 ThreadLocal:n 个实例

很显然,如果使用 ThreadLocal 的话,线程应该是能复用的,否则和 new 的效果是一样滴

再看加锁和池的差别,主要表现在并发性能方面,加锁实际导致了串行化,不满足高并发的场合

再看下池和 ThreadLocal的差别

3. 并发性能上2者都很优秀

4. 相对来说 ThreadLocal 的实现更简单,而且完全不需要加锁或同步,而池在管理池内的元素时免不了需要加锁或同步

5. 池的复用性最好,而 ThreadLocal 的复用性则完全取决于其宿主线程的复用性

到此这篇关于Java中的SimpleDateFormat的线程安全问题详解的文章就介绍到这了,更多相关SimpleDateFormat线程安全问题内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决spring cloud gateway调用其他模块出现的问题

    解决spring cloud gateway调用其他模块出现的问题

    这篇文章主要介绍了解决spring cloud gateway调用其他模块出现的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-06-06
  • idea中的lombok不生效的四种解决方法

    idea中的lombok不生效的四种解决方法

    Lombok项目是一个java库,它可以自动插入到编辑器和构建工具中,本文将详细给大家介绍idea中的lombok不生效的四种解决方法,需要的朋友可以参考下
    2023-05-05
  • Java基于Socket实现HTTP下载客户端

    Java基于Socket实现HTTP下载客户端

    这篇文章主要介绍了Java基于Socket实现HTTP下载客户端的相关资料,感兴趣的小伙伴们可以参考一下
    2016-01-01
  • 解决mapper接口无法映射mapper.xml的问题

    解决mapper接口无法映射mapper.xml的问题

    这篇文章主要介绍了解决mapper接口无法映射mapper.xml的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • Spring Boot 集成 Sharding-JDBC + Mybatis-Plus 实现分库分表功能

    Spring Boot 集成 Sharding-JDBC + Mybatis-Plus 实现分库分表功能

    这篇文章主要介绍了Spring Boot 集成 Sharding-JDBC + Mybatis-Plus 实现分库分表功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • Spring JDBC配置与使用的实现

    Spring JDBC配置与使用的实现

    本文主要介绍了Spring JDBC配置与使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-06-06
  • 教你怎么用idea创建web项目

    教你怎么用idea创建web项目

    好多朋友在使用IDEA创建项目时,总会碰到一些小问题.现在我们就演示一下使用IDEA创建web项目的完整步骤吧.文中有非常详细的图文示例哦,,需要的朋友可以参考下
    2021-05-05
  • Java 实战项目之小说在线阅读系统的实现流程

    Java 实战项目之小说在线阅读系统的实现流程

    读万卷书不如行万里路,只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用java+SSM+jsp+mysql+maven实现前台阅读后台管理的小说在线阅读系统,大家可以在过程中查缺补漏,提升水平
    2021-11-11
  • Java中使用HttpPost发送form格式的请求实现代码

    Java中使用HttpPost发送form格式的请求实现代码

    在Java中使用HttpPost发送form格式的请求,可以使用Apache HttpClient库来实现,这篇文章主要介绍了Java中使用HttpPost发送form格式的请求,本文给大家展示示例代码,需要的朋友可以参考下
    2023-08-08
  • 详细说一说Java序列化的几种方式对比

    详细说一说Java序列化的几种方式对比

    在某种情况下需要考虑一些安全问题和数据对象的使用问题,这时候就可以用序列化的技术来进行数据存储,这篇文章主要介绍了Java序列化几种方式对比的相关资料,需要的朋友可以参考下
    2025-07-07

最新评论