SimpleDateFormat在多线程下的安全问题

news2024/11/25 23:46:12

目录

情景重现

SimpleDateFormat解析

解决方案

局部变量

 加锁

 使用线程变量

使用DateTimeFormatter 


情景重现

SimpleDateFormat类是Java开发中的一个日期时间的转化类。它可以满足绝大多数的开发场景,但是在高并发下会出现并发问题。接下来查看下文中的案例。

public class TestSimpleDateFormat {
    public static void main(String[] args) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    Date parse = format.parse("2003-01-01");
                    System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

上面代码简单来说就是创建了一个SimpleDateFormat类对象,该对象被后续会被五个线程使用,去转化日期格式并打印。我们来查看输出结果。

java.lang.NumberFormatException: empty String
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at test.lambda$main$0(test.java:21)
	at java.lang.Thread.run(Thread.java:745)
java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at test.lambda$main$0(test.java:21)
	at java.lang.Thread.run(Thread.java:745)
java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at test.lambda$main$0(test.java:21)
	at java.lang.Thread.run(Thread.java:745)
java.lang.NumberFormatException: empty String
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at test.lambda$main$0(test.java:21)
	at java.lang.Thread.run(Thread.java:745)
Fri Nov 01 00:00:00 CST 2222

可以看到,只输出了一次时间转化,并且该输出格式还是错误的。接下来我们来查看为什么SimpleDateFormat类是线程不安全的。

SimpleDateFormat解析

我们根据parse()方法,查看SimpleDateFormat是如何进行格式转换的。

我们可以看到,返回结果是根据另一个方法获取到的,接下来我们接着查看该parse()源码。

 这是一个抽象方法,接着我们去查看它的具体实现。

可以看到该方法很长,但是我们只关注返回如何结果,直接拉到最后查看该方法如何返回一个日期格式 。上图中,最后一次修改parseDate对象是在箭头的位置。那么我们查看getTime()方法。

可以看到该方法是由Calendar类提供的,该类名翻译为中文就是日历的意思,并且返回结果也是我们需要的日期格式,那么我们就可以确定该方法用于给parseDate对象提供返回值的,接下来回退一下查看其他方法哪个是提供Calendar对象来调用getTime()方法的。

现在我们清楚了Calendar类对象是由establish()方法提供的了,该方法中需要一个参数calendar对象。该对象由SimpleDateFormat的父类DateFormat来维护。

此时我们或许大概明白是因为SimpleDateFormat类之所以线程不安全的问题是因为在多线程下共享了calendar对象。接下来我们继续查看establish()方法,验证是否是这样,下面是具体源码

    Calendar establish(Calendar cal) {
        boolean weekDate = isSet(WEEK_YEAR)
                       && field[WEEK_YEAR] > field[YEAR];
        if (weekDate && !cal.isWeekDateSupported()) {
            // Use YEAR instead
            if (!isSet(YEAR)) {
                set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
            }
            weekDate = false;
        }

        cal.clear();
        // Set the fields from the min stamp to the max stamp so that
        // the field resolution works in the Calendar.
        for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
            for (int index = 0; index <= maxFieldIndex; index++) {
                if (field[index] == stamp) {
                    cal.set(index, field[MAX_FIELD + index]);
                    break;
                }
            }
        }

        if (weekDate) {
            int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1;
            int dayOfWeek = isSet(DAY_OF_WEEK) ?
                                field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
            if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) {
                if (dayOfWeek >= 8) {
                    dayOfWeek--;
                    weekOfYear += dayOfWeek / 7;
                    dayOfWeek = (dayOfWeek % 7) + 1;
                } else {
                    while (dayOfWeek <= 0) {
                        dayOfWeek += 7;
                        weekOfYear--;
                    }
                }
                dayOfWeek = toCalendarDayOfWeek(dayOfWeek);
            }
            cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek);
        }
        return cal;
    }

可以看到,在该方法中,对cal对象执行了clear()方法与set()方法,我们查看clear方法是做什么的

该方法提供了类似初始化的功能,将上一次的格式转化保存的cal属性清除。

而set()方法将本次的格式转换需要的数据更新。因此,我们可以确定了SimpleDateFormat类之所以线程不安全就是因为共享了calendar对象。

解决方案

为了避免SimpleDateFormat格式转换带来的并发问题,我们可以采取以下几个措施

局部变量

我们已经知道了产生线程安全问题的原因是共享了相同属性,那么我们只要让每个线程都包含自己的属性就可以避免该问题的发生。具体实现代码如下

public class TestSimpleDateFormat {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
        new Thread(()->{
            try {
                    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                    Date parse = format.parse("2003-01-01");
                    System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

 运行结果如下

Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003

这种方式不太推荐,因为会创建大量的SimpleDateFormat对象,占用内存空间。 

 加锁

除了让每个线程都拥有自己独立的对象外,我们也可以保证在同一时刻下,只有一个线程对共享属性进行修改,那就是加锁。具体实现代码如下

public class TestSimpleDateFormat{
    public static void main(String[] args) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    Date parse;
                    synchronized (format){
                        parse = format.parse("2003-01-01");
                    }
                    System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

但是这种方法不太推荐,因为能够出现格式转化错误的情况已经是很大的并发了,如果还使用同步锁的话会影响性能。

 使用线程变量

public class test {
    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    Date parse = threadLocal.get().parse("2023-01-01");
                    System.out.println(parse);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

使用DateTimeFormatter 

在JDK8之后提供了线程安全的格式转化DateTimeFormatter类,使用方法如下

public class test {
    public static void main(String[] args) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    TemporalAccessor parse = dateTimeFormatter.parse("2003-06-03");
                        System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

输出结果为

{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1267592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

导出CSV文件

从数据库导出csv文件 从HeidiSQL 导数据出来成.csv文件 SELECT * FROM csv INTO OUTFILE C:\\feiniu\\note\\csv\\demo.csv fields terminated by , CSV是什么 跟Excel表差不多 csv与excel对比&#xff1a; csv只能用于存储纯文本内容&#xff0c;excel不仅支持纯文本内容…

Fwupd 1.9.9 发布

Fwupd 1.9.9 开源 Linux 固件升级工具今天发布了另一个维护更新&#xff0c;解决了各种错误并扩展了硬件支持。 继 fwupd 1.9.8 发布不到一周&#xff0c;fwupd 1.9.9 版本又推出了对联想 ThinkPad X1 Yoga Gen7 530E 二合一笔记本电脑和研华 BMC 设备的支持&#xff0c;以及对…

【目标跟踪】光流跟踪(python、c++代码)

文章目录 前言一、代码流程与思路二、python 代码2.1 代码详解2.2 完整代码 三、c 代码四、结果展示 前言 光流利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的对应关系&#xff0c;从而计算出相邻帧之间物体的运动信息的一种方法。…

OSHI-操作系统和硬件信息库

文章目录 引言一、快速入门1.1 OSHI的简介1.2 引入依赖1.3 涉及的包&#xff08;package&#xff09;1.4 涉及的核心类 二、操作系统信息&#xff1a;OperatingSystem2.1 总揽2.2 文件系统信息&#xff1a;FileSystem2.3 网络参数信息&#xff1a;NetworkParams2.4 进程信息&am…

【2023年APMCM亚太杯C题】完整代码+结果分析+论文框架(二)

2023年APMCM亚太杯C题 3、4问 问题三问题分析技术文档基于相关性分析的汽车产业影响分析3.1 分布检验模型的建立3.2 相关性模型的建立3.3 模型求解 问题四问题分析技术文档4 基于 Kruskal-Wallis H 检验的政策影响研究4.1 分布检验模型的建立4.2 方差齐性检验模型的建立4.3 Kru…

软件测试人员如何快速成长?

文章标题有点大&#xff0c;更贴切的描述应该是测试人员如何在工作中快速积累经验和提高技能。但是这么描述太长了&#xff0c;根据自己的工作经验和经历&#xff0c;谈一些个人观点。 在这我也准备了一份软件测试视频教程&#xff08;含接口、自动化、性能等&#xff09;&…

EXCEL小技巧, 用2种公式方法,查找1列数据里符合条件的最后1个单元格

目录 1 问题 2 解决办法 3 lookup()变形公式&#xff0c; lookup(1,0/((列1条件1)*(列2条件2)) 3.1 公式用法 3.2 局限性 4 数组公式&#xff0c;INDEX(H:H,MAX(IF(I:IK4,ROW(H:H)))) 4.1 语法 1问题 一般来说&#xff0c;EXCEL里使用 match()等只能查到符合条件的第一…

作为用户,推荐算法真的是最优解么?

前言 众所周知&#xff0c;随着互联网技术的发展&#xff0c;推荐算法也越来越普及。无论是购物网站、社交媒体平台还是在线影视平台&#xff0c;推荐算法已成为用户获取相关信息的主要途径。据悉&#xff0c;近期GitHub决定结合算法推荐&#xff0c;将“Following”和“For Yo…

Django连接数据库

连接数据库 接下来讲的几个步骤不需要区分先后&#xff0c;但都得进行操作 settings.py的操作 #!settings.py ....别的代码DATABASES {default: { ENGINE: django.db.backends.mysql, NAME: day1121, # 数据库名字&#xff0c;要先创建好 USER: root, PASSWORD: 123456, HO…

解决Unable to preventDefault inside passive event listener invocation.报错

报错信息&#xff1a; 这个报错大致说的是&#xff1a;无法在被动事件侦听器调用中防止Default 查了其他博主的解决办法&#xff1a;比如&#xff1a; 1、声明事件监听的时候设置为主动事件监听&#xff1a; window.addEventListener(‘touchmove’, handler, { passive: fal…

【方法】PowerPoint如何删除“限制编辑”?

如果PPT文件设置成“只读模式”&#xff0c;就会被限制编辑&#xff0c;也就是无法对PPT进行编辑或更改&#xff0c;那要如何删除这个“限制”呢&#xff1f; 下面小编会按照“无密码的只读方式”、“有密码的只读方式”以及“忘记了密码的只读方式”这3种情况&#xff0c;来说…

LabVIEW使用软件定义进行汽车电子测试

LabVIEW使用软件定义进行汽车电子测试 电子元件的逻辑和稳健性一直都是需要评估的对象。过去&#xff0c;汽车仅使用几种电子元件来执行简单的功能&#xff0c;每个元件都是在专门准备的环境中单独进行的。但随着电子元件日益多样化&#xff0c;且功能日益复杂&#xff0c;这种…

Vue修改密码功能的源代码

基本需求 输入框不能为空 旧密码表单提交时必须正确 两次输入新密码一致 限定新密码的复杂度&#xff0c;这里是长度在 6 到 20 个字符 <template><el-form ref"form" :model"user" :rules"rules" label-width"80px"><…

postgres在docker中使用

记录个人开发过程中postgres在docker中的使用&#xff0c;以便后续查看。 Dockerfile 个人是在M1电脑上开发&#xff0c;所以platform使用linux/amd64来兼容amd芯片。 FROM --platformlinux/amd64 postgres:16.1-alpine COPY ./poetrydb.sql /docker-entrypoint-initdb.d/po…

Jira Software最新版本(9.11.2)安装

软件获取 Jira Software 历史版本下载地址&#xff1a;Jira Server 下载存档 | Atlassian Atlassian-agent.jar https://github.com/haxqer/confluence/releases/download/v1.3.3/atlassian-agent.jar MySQL 驱动包 MySQL :: Download MySQL Connector/J (Archived Versio…

用Sublime编写Lua脚本

大家好&#xff0c;我是阿赵。   现在很多手游项目使用lua作为热更新的代码脚本&#xff0c;我一直很喜欢用Sublime来写lua程序。喜欢使用它的原因是它的轻量化&#xff0c;因为我经常要同时打开多个项目&#xff0c;Unity和VisualStudio这些软件都比较占用电脑的性能&#x…

Pycharm修改文件默认打开方式 + CSV Editor插件使用

1、File —> Settings —> Editor —> File Types 然后将*csv添加到最上面 在plugins中下载插件&#xff0c;CSV Editor 备注&#xff1a;不在上一步的“File Types”中将*.csv设置为CSV格式&#xff0c;插件是不起作用的 就可以使用了

微服务学习|初识elasticsearch、操作索引库、文档操作、RestClient操作索引库、RestClient操作文档

初识elasticsearch 什么是elasticsearch&#xff1f; elasticsearch是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容。 elasticsearch结合kibana、Logstash、Beats&#xff0c;也就是elastic stack (ELK)。被广泛应用在日志数据分析、实…

图书管理系统源码,图书管理系统开发,图书借阅系统源码配置和运行图解源码已附加

目录 配置简介和软件条件 数据库附件配置 vs应用程序web.config配置数据库链接字符串 数据库文件脚本代码 配置简介和软件条件 所需要的软件是Vs2017以上数据库是Sqlserver2012以上&#xff0c;如果数据库附件不了可以使用数据库脚本附件数据库脚本会在文章末尾写出来。可以…

TableAgent:首个国产可私有部署的企业级Code Interpreter

TableAgent公测地址&#xff1a;https://tableagent.DataCanvas.com 数字化时代&#xff0c;数据分析的重要性犹如空气般无处不在。商业数据分析是数字化管理、智能决策的基础&#xff0c;同时数据分析又是一个专业性极强的工作&#xff0c;描述性分析、诊断性分析、预测性分…