SimpleDateFormat在多线程下的安全问题

目录

情景重现

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 Stringat 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 pointsat 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 pointsat 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 Stringat 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 insteadif (!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>() {@Overrideprotected 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.rhkb.cn/news/204991.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php的字符转义函数有那些,是干什么的

在 PHP 中&#xff0c;字符转义函数是用于处理字符串中的特殊字符&#xff0c;以防止这些字符被误解、滥用或引起安全问题的一组函数。这些函数的主要作用是确保在将用户提供的数据插入到数据库、构建 HTML 输出或进行其他与安全相关的操作时&#xff0c;不会导致潜在的安全漏洞…

enum常用方法 - Java

六、enum常用方法 0、准备工作1、name()2、ordinal()3、values()4、valueOf()5、compareTo()6、toString() 说明&#xff1a;使用关键字enum时&#xff0c;会隐式 继承 Enum类&#xff0c;这样我们就可以使用 Enum 类相关的方法。 0、准备工作 enum Season2 {SPRING("…

Django快速搭建静态网页

Django的快速搭建 这个是例子 这个是一个目录 项目名称&#xff1a;项目似乎被命名为DJ0928&#xff0c;这是Django项目的根目录。 文件都是Django项目的核心配置文件。 settings.py 包含了项目的配置设置。urls.py 定义了项目的URL路由。wsgi.py 和 asgi.py 分别用于Web服务器…

springboot集成springsecurity

转载自&#xff1a;www.javaman.cn 1、整合springsecurity 添加pom.xml <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency>2、springsecurity认证授权流程…

uniapp挽留提示2.0

项目需求&#xff1a;有时候挽留的ui是全屏的&#xff0c;用page-container也可以。后来产品提了个问题&#xff0c;手机侧滑的时候没那么顺畅&#xff08;就是一用侧滑&#xff0c;就显示出来&#xff0c;产品要的方案是如下图&#xff0c;emmm大概是这个意思&#xff09; 后面…

网络协议系列:TCP三次握手,四次挥手的全过程,为什么需要三次握手,四次挥手

TCP三次握手&#xff0c;四次挥手的全过程&#xff0c;为什么需要三次握手&#xff0c;四次挥手 一. TCP三次握手&#xff0c;四次挥手的全过程&#xff0c;为什么需要三次握手&#xff0c;四次挥手前言TCP协议的介绍三次握手三次握手流程&#xff1a;1. A 的 TCP 向 B 发送 连…

爬虫项目实战:利用基于selenium框架的爬虫模板爬取豆瓣电影Top250

&#x1f44b; Hi, I’m 货又星&#x1f440; I’m interested in …&#x1f331; I’m currently learning …&#x1f49e; I’m looking to collaborate on …&#x1f4eb; How to reach me … README 目录&#xff08;持续更新中&#xff09; 各种错误处理、爬虫实战及模…

浏览器插api开发文档

chrome谷歌浏览器开发文档

软著项目推荐 深度学习二维码识别

文章目录 0 前言2 二维码基础概念2.1 二维码介绍2.2 QRCode2.3 QRCode 特点 3 机器视觉二维码识别技术3.1 二维码的识别流程3.2 二维码定位3.3 常用的扫描方法 4 深度学习二维码识别4.1 部分关键代码 5 测试结果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天…

Java后端开发——JDBC(万字详解)

Java后端开发——JDBC&#xff08;万字详解&#xff09; 今日目标 掌握JDBC的的CRUD理解JDBC中各个对象的作用掌握Druid的使用 1&#xff0c;JDBC概述 在开发中我们使用的是java语言&#xff0c;那么势必要通过java语言操作数据库中的数据。这就是接下来要学习的JDBC。 1.1 …

GitHub桌面版

GitHub桌面版 一、GitHub 桌面版二、clone 仓库三、更新仓库 一、GitHub 桌面版 二、clone 仓库 三、更新仓库

Spring面向切面编程(AOP);Spring控制反转(IOC);解释一下Spring AOP里面的几个名词;Spring 的 IoC支持哪些功能

文章目录 Spring面向切面编程(AOP)什么是AOPSpring AOP and AspectJ AOP 的区别&#xff1f;Spring AOP中的动态代理如何理解 Spring 中的代理&#xff1f;解释一下Spring AOP里面的几个名词Spring在运行时通知对象Spring切面可以应用5种类型的通知&#xff1a;什么是切面 Aspe…

Modbus TCP

Modbus &#xff08;&#x1f446; 百度百科&#xff0c;放心跳转&#xff09; 起源 Modbus 由 Modicon 公司于 1979 年开发&#xff0c;是一种工业现场总线协议标准。 Modbus 通信协议具有多个变种&#xff0c;支持串口&#xff0c;以太网多个版本&#xff0c;其中最著名的…

一种方便、优美的使用Python调用fofa API的方法

免责声明&#xff1a;由于传播或利用此文所提供的信息、技术或方法而造成的任何直接或间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c; 文章作者不为此承担任何责任。 学习网络安全的过程中&#xff0c;绕不开fofa搜索&#xff0c;我的需求是使用fofa获取互联网所…

安防监控视频融合平台EasyCVR定制化页面开发

安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存储、回放与检索…

牛客 算法题 【HJ102 字符统计】 golang实现

题目 HJ102 字符统计 golang代码实现 package mainimport ("bufio""fmt""os""sort" )func main() {// str_arry :make([]string, 0)str_map : make(map[rune]int)result_map : make(map[int][]string)scanner : bufio.NewScanner(os…

基于mvc电影院售票预订选座系统php+vue+elementui

本影院售票系统主要包括二大功能模块&#xff0c;管理员功能模块和用户功能模块。 &#xff08;1&#xff09;管理员模块&#xff1a;系统中的核心用户管理员登录后&#xff0c;通过管理员功能来管理后台系统。主要功能有&#xff1a;首页、个人中心、电影类型管理、场次时间管…

GitHub上8个强烈推荐的 Python 项目

文章目录 前言1. Manim2. DeepFaceLab3. Airflow4. GPT-25. XSStrike6. 谷歌图片下载7. Gensim8. SocialMapper总结关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③…

高等数学零基础篇复习笔记

预备章 零基础高等数学入门知识 第一节 集合、运算与关系 第二节 三角函数与反三角函数 三角函数的公式 反三角函数 第三节 常见不等式及数列 划重点 第一章 函数、极限与连续 第一节 函数及函数的初等特性 特殊函数 反函数 函数的初等特性 ①有界性 ②奇偶性 偶函数图像…

谈谈中间件设计的思路

前言 想要设计和真正理解中间件的架构理论和思想。对于开发来说需要具备三个关键的能力 1&#xff1a;基础通用技术的深入理解和运用2&#xff1a;了解和熟悉常见中间件的设计思想&#xff0c;且有自己的感悟,并且能按照自己的理解模仿写一写3&#xff1a;业务的高度理解能力…