【SimpleDateFormat】线程不安全问题分析及解决方案

前言

在日常开发中,我们经常需要去做日期格式转换,可能就会用到SimpleDateFormat类。但是,如果使用不当,就很容易引发生产事故

1. 问题推演

1.1 初始日期工具类

刚开始的日期转换工具类可能长这样:

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

1.2 引入线程安全问题

这时候,就有人要说了,以上的代码存在问题,每次调用的使用,都要创建SimpleDateFormat,在频繁使用时,就会创建大量的对象。

所以将代码改造成了这样:

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

在这里,看似优化了性能,不管被调用多少次,都只有一个SimpleDateFormat对象,但是却引入了线程安全问题

1.3 并发问题示例

public class TestDateUtil {public static void main(String[] args) throws InterruptedException {// 创建线程池ExecutorService executorService = Executors.newFixedThreadPool(10);Date date1 = new Date(3600);Date date2 = new Date(36000);// 调用次数int n = 10;for (int i = 0; i < n; i++) {int finalI = i;executorService.execute(() -> {if (finalI % 2 == 0) {System.out.println("Date为:" + date1 + " 转换结果为:" + DateUtil.formatDate(date1));} else {System.out.println("Date为:" + date2 + " 转换结果为:" + DateUtil.formatDate(date2));}});}// 等待执行结果executorService.shutdown();}
}

输出结果:

Date为:Thu Jan 01 08:00:03 CST 1970 转换结果为:1970-01-01 08:00:03
Date为:Thu Jan 01 08:00:36 CST 1970 转换结果为:1970-01-01 08:00:36
Date为:Thu Jan 01 08:00:03 CST 1970 转换结果为:1970-01-01 08:00:03
Date为:Thu Jan 01 08:00:36 CST 1970 转换结果为:1970-01-01 08:00:03 // 错误结果
Date为:Thu Jan 01 08:00:36 CST 1970 转换结果为:1970-01-01 08:00:03 // 错误结果
Date为:Thu Jan 01 08:00:36 CST 1970 转换结果为:1970-01-01 08:00:03 // 错误结果
Date为:Thu Jan 01 08:00:36 CST 1970 转换结果为:1970-01-01 08:00:03 // 错误结果
Date为:Thu Jan 01 08:00:03 CST 1970 转换结果为:1970-01-01 08:00:36 // 错误结果
Date为:Thu Jan 01 08:00:03 CST 1970 转换结果为:1970-01-01 08:00:36 // 错误结果
Date为:Thu Jan 01 08:00:03 CST 1970 转换结果为:1970-01-01 08:00:03

可以看到上方出现了各种转换问题,【Thu Jan 01 08:00:36 CST 1970】的数据被转换成了【1970-01-01 08:00:03】。

1.4 阿里巴巴规范

阿里巴巴规范也提出,不要SimpleDateFormat定义为static变量

image-20231003235317342

2. 问题分析

查看源码,分析问题。

image-20231003225715789

image-20231003230025194

因为在SimpleDate类中,使用了成员变量在方法中进行传参调用,在多线程之间并发set、get中,很容易就产生了线程安全问题。

3. 解决方法

3.1 使用局部变量

使用局部变量,即最开始的用法,每一次都创建自己的SimpleDateFormat对象,即可解决并发问题

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

缺点:在高并发情况下会创建很多的对象,不推荐。

3.2 synchronized锁

使用synchronized对存在线程安全的代码块进行同步处理

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

缺点:同一个时刻,只能有个一个线程执行format方法,性能比较差

3.3 ThreadLocal方式

使用ThreadLocal每个线程持有自己的SimpleDateFormat,解决多线程之间并发问题

public class DateUtil {// 创建 ThreadLocal 对象,并设置默认值(new SimpleDateFormat)private static ThreadLocal<SimpleDateFormat> threadLocal =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public static String formatDate(Date date) {return threadLocal.get().format(date);}
}

3.4 使用DateTimeFormatter

以上方案都是因为SimpleDateFormat线程不安全导致我们需要去特殊处理,但在JDK 8之后,可以直接使用线程安全类DateTimeFormatter

使用 DateTimeFormatter 必须要配合 JDK 8 中新增的时间对象 LocalDateTime 来使用,因此在操作之前,我们可以先将 Date 对象转换成 LocalDateTime,然后再通过 DateTimeFormatter 来格式化时间,具体实现代码如下:

public class DateUtil {// 创建 DateTimeFormatter 对象private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");public static String formatDate(Date date) {// 将 Date 转换成 JDK 8 中的时间类型 LocalDateTimeLocalDateTime localDateTime =LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());return dateTimeFormatter.format(localDateTime);}
}

4. 各方案优缺点总结

如果是使用JDK 8+,则直接使用DateTimeFormatter即可。如果使用的是低版本的JDK,则可以使用TheadLocalsynchronized解决方案。

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

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

相关文章

设计模式2、抽象工厂模式 Abstract Factory

解释说明&#xff1a;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定他们具体的类。 简言之&#xff0c;一个工厂可以提供创建多种相关产品的接口&#xff0c;而无需像工厂方法一样&#xff0c;为每一个产品都提供一个具体工厂 抽象工厂&#xff08;Abstra…

知识图谱02——使用python将信息录入neo4j

将文档传入chatgpt&#xff0c;生成对应的cypher语句 链接: https://pan.baidu.com/s/1Ny-ttbBSpqYEigwYiCWMeA?pwdc7sc 提取码: c7sc 使用命令行安装对应的包 pip install neo4jchatgpt生成出的txt文档中的内容如下&#xff1a; MERGE (Node1:Entity {name: 原始舱单提运单…

Redis BitMap+SpringBoot 实现签到与统计功能

前言&#xff1a; 在各个项目中&#xff0c;我们都可能需要用到签到和 统计功能。签到后会给用户一些礼品以此来吸引用户持续在该平台进行活跃。 签到功能&#xff0c;使用 Redis 中的 BitMap 功能来实现&#xff0c;就是一个非常不错的选择。 一、Redis BitMap 基本用法 Bi…

如何在 Elasticsearch 中使用 Openai Embedding 进行语义搜索

随着强大的 GPT 模型的出现&#xff0c;文本的语义提取得到了改进。 在本文中&#xff0c;我们将使用嵌入向量在文档中进行搜索&#xff0c;而不是使用关键字进行老式搜索。 什么是嵌入 - embedding&#xff1f; 在深度学习术语中&#xff0c;嵌入是文本或图像等内容的数字表示…

nodejs+vue晓海网上订餐系统elementui

管理员功能需求 管理员登陆后&#xff0c;主要模块包括首页、个人中心、用户管理、菜单信息管理等功能。 第三章 系统分析 10 3.1需求分析 10 3.2可行性分析 10 3.2.1技术可行性&#xff1a;技术背景 10 3.2.2经济可行性 11 3.2.3操作可行性&#xff1a; 11 3.3性能分析 11 3.4…

Kubernetes(K8s):容器编排的未来是什么?

文章目录 Kubernetes的核心概念和工作原理1. 节点&#xff08;Nodes&#xff09;2. 容器3. Pod4. 控制器5. 服务 Kubernetes为什么成为容器编排的首选工具&#xff1f;1. 自动化和可扩展性2. 多云支持3. 生态系统和社区4. 云原生开发 未来趋势&#xff1a;K8s如何继续发展和演进…

(三) Markdown插入互联网或本地视频解决方案

前言 不论博客系统是WordPress还是Typecho&#xff0c;绕不开的是两种书写语言&#xff0c;一种称之为富文本&#xff0c;一种叫做Markdown。 Markdown有很多好处&#xff0c;也有很多坏处&#xff0c;比如Markdown本身不具备段落居中的功能&#xff0c;以及Markdown也不具有…

osg实现鼠标框选

目录 1. 需求的提出 2. 具体实现 2.1. 禁止场景跟随鼠标转动 2.2. 矩形框前置绘制 3. 附加说明 3.1. 颜色设置说明 3.2.矩形框显示和隐藏的另一种实现 1. 需求的提出 有时需要在屏幕通过按住键盘上的某个键如Ctrl键且按住鼠标左键&#xff0c;拖出一个矩形&#xff0c;实现框…

Java自学(二)

目录 一、数组逆置&#xff08;临时变量法&#xff09; 二、基本类型和引用类型传参的区别 一、数组逆置&#xff08;临时变量法&#xff09; 二、基本类型和引用类型传参的区别 基本类型传参&#xff0c;形参一般不会改变实参。 形参是实参的一份数据拷贝&#xff0c;改变形…

(unordered)map和set封装(底层红黑树)

map和set封装 文章目录 map和set封装设计问题&#xff08;知其所以然&#xff09;为什么要对iterator进行封装&#xff1f;为什么要引入Self Ref Ptr这些模板参数&#xff1f;为什么是试图从non_const转变为const&#xff0c;而不是const转为non_const如何解决 为什么说能加con…

黑马头条项目环境搭建

注册中心网关配置 spring:cloud:gateway:globalcors:add-to-simple-url-handler-mapping: truecorsConfigurations:[/**]:allowedHeaders: "*"allowedOrigins: "*"allowedMethods:- GET- POST- DELETE- PUT- OPTIONroutes:# 平台管理- id: useruri: lb://…

Redis最常见的5种应用场景

Redis作为当今最流行的内存数据库&#xff0c;已经成为服务端加速的必备工具之一。对于Redis为什么那么快&#xff1f;以及Redis采用单线程&#xff0c;但为什么反而获得更高的性能的疑问&#xff0c;在之前的Redis为什么那么快&#xff1f;一文中&#xff0c;已经有所介绍。 …

postgresql-自增字段

postgresql-自增字段 标识列IdentitySerial类型Sequence序列 标识列Identity -- 测试表 create table t_user( -- 标识列自增字段user_id integer generated always as identity primary key,user_name varchar(50) not null unique );-- 自动生成序列 CREATE SEQUENCE public…

专业PDF编辑阅读工具PDF Expert mac中文特点介绍

PDF Expert mac是一款专业的PDF编辑和阅读工具。它可以帮助用户在Mac、iPad和iPhone等设备上查看、注释、编辑、填写和签署PDF文档。 PDF Expert mac软件特点 PDF编辑&#xff1a;PDF Expert提供了丰富的PDF编辑功能&#xff0c;包括添加、删除、移动、旋转、缩放、裁剪等操作…

Ai4science学习、教育和更多

11 学习、教育和更多 人工智能的进步为加速科学发现、推动创新和解决各个领域的复杂问题提供了巨大的希望。然而&#xff0c;要充分利用人工智能为科学研究带来的潜力&#xff0c;我们需要面对教育、人才培养和公众参与方面的新挑战。在本节中&#xff0c;我们首先收集了关于每…

Java下正面解除警告Unchecked cast: ‘java.lang.Object‘ to ‘java.util.ArrayList‘

就是我在反序列化时&#xff0c;遇到这样一个警告&#xff1a; Unchecked cast: java.lang.Object to java.util.ArrayList<com.work1.Student>然后我去网上查&#xff0c;有些人说用SuppressWarnings(“unchecked”)去忽略警告&#xff0c;但是我觉得作为一名合格的程序…

阅读LINGO-1: Exploring Natural Language for Autonomous Driving

1 背景2 Motivation3 具体过程 1 背景 wayve在9月14日公布了大语言模型和自动驾驶的结合模型LINGO-1&#xff0c;可以用自然语言解释自动驾驶的决策原因。 网页链接&#xff1a;https://wayve.ai/thinking/lingo-natural-language-autonomous-driving/ 但是目前没有论文和开源…

力扣 -- 279. 完全平方数(完全背包问题)

解题步骤&#xff1a; 参考代码&#xff1a; 未优化代码&#xff1a; class Solution { public:int numSquares(int n) {const int INF0x3f3f3f3f;int msqrt(n);//多开一行&#xff0c;多开一列vector<vector<int>> dp(m1,vector<int>(n1));//初始化第一行…

证书显示未受信任,生成的证书过期

此时若是导入证书后&#xff0c;证书显示未受信任&#xff0c;则说明我们缺失最新的AppleWWDRCA证书 解决方案&#xff1a; 重新下载AppleWWDRCA并安装。即下载最新的AppleWWDRCA证书&#xff0c;双击安装到“登录”项的钥匙串下&#xff1b;然后再安装你的开发证书或者发布证书…

MySQL-MVCC(Multi-Version Concurrency Control)

MySQL-MVCC&#xff08;Multi-Version Concurrency Control&#xff09; MVCC&#xff08;多版本并发控制&#xff09;&#xff1a;为了解决数据库并发读写和数据一致性的问题&#xff0c;是一种思想&#xff0c;可以有多种实现方式。 核心思想&#xff1a;写入时创建行的新版…