并发场景使用SimpleDateFormat异常问题和解决

news2024/11/25 20:28:29

SimpleDateFormat类主要是负责日期的格式化与转换操作,因为它不是线程安全的,所以使用SimpleDateFormat时,务必确保同一个SimpleDateFormat对象不要与其他线程共享,否则并发情况下会出现问题

目录

    • 异常示例
    • 解决方案1:创建了多个SimpleDateFormat类的实例
    • 解决方案2:使用ThreadLocal类
    • 解决方案3:使用DateTimeFormatter处理(JDK1.8)

异常示例

演示一下SimpleDateFormat在多线程情况下报错问题,先创建一个线程,在run方法中转换日期

public class ErrorTest extends Thread{
    private final SimpleDateFormat sdf;
    private final String date;
    public ErrorTest(SimpleDateFormat sdf, String date) {
        super();
        this.sdf = sdf;
        this.date = date;
    }
    @Override
    public void run() {
        try {
            String newDate = sdf.format(sdf.parse(date));
            if (!newDate.equals(date)) {
                System.out.println(this.getName() + "报错了! " +
                        "日期:" + date + " 转换成了:" + newDate);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

写完发现在idea开发中就已经开始报警告了

在这里插入图片描述

下面在启动类中同时传多个日期去转换

public class Run {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String[] dateArray = new String[] { "2023-01-01", "2023-01-02",
                "2023-01-03", "2023-01-04", "2023-01-05", "2023-01-06", "2023-01-07"};
        int length = dateArray.length;
        ErrorTest[] array = new ErrorTest[length];
        for (int i = 0; i < length; i++) {
            array[i] = new ErrorTest(sdf, dateArray[i]);
        }
        for (int i = 0; i < length; i++) {
            array[i].start();
        }
    }
}

输出:

启动后可以看到控制台报错了,或者是日期转换成了其他莫名其妙的数据,说明SimpleDateFormat在多线程下极易出现日期转换错误的问题

解决方案1:创建了多个SimpleDateFormat类的实例

创建一个类,在每次执行线程时new一个新的SimpleDateFormat对象,让每一个线程享用一个SimpleDateFormat对象

public class SolveTest1 extends Thread{
    private final String date;
    public SolveTest1(String date) {
        super();
        this.date = date;
    }
    @Override
    public void run() {
        try {
        	// 转换
            Date dateD = new SimpleDateFormat("yyyy-MM-dd").parse(date);
            // 打印
            String newDate = new SimpleDateFormat("yyyy-MM-dd").format(dateD);
            if (newDate.equals(date)) {
                System.out.println(this.getName() + "成功了! " +
                        "日期:" + date + " 转换成了:" + newDate);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

启动类不再传SimpleDateFormat对象

/**
* ThreadLocal
*/
public class Run {
    public static void main(String[] args) {
        String[] dateArray = new String[] { "2023-01-01", "2023-01-02",
                "2023-01-03", "2023-01-04", "2023-01-05", "2023-01-06", "2023-01-07"};
        int length = dateArray.length;
        SolveTest1[] array = new SolveTest1[length];
        for (int i = 0; i < length; i++) {
            array[i] = new SolveTest1(dateArray[i]);
        }
        for (int i = 0; i < length; i++) {
            array[i].start();
        }
    }
}

输出可以看到七次都转换成功了

在这里插入图片描述

解决方案2:使用ThreadLocal类

ThreadLocal简介:每个Thread线程内部都有一个ThreadLocalMap,以线程作为key,要储存的值作为value,所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。这种变量在多线程环境下通过getset方法访问时能保证各个线程的变量相对独立于其他线程内的变量

下面创建一个类来使用ThreadLocal,记得使用完要调用remove()方法回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。

/**
* ThreadLocal
*/
public class SolveTest2 extends Thread{
    private final String date;
    private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = new ThreadLocal<>();

    public SolveTest2(String date) {
        super();
        this.date = date;
    }
    @Override
    public void run() {
        try {
            // 转换
            Date dateD = getSimpleDateFormat("yyyy-MM-dd").parse(date);
            // 打印
            String newDate = getSimpleDateFormat("yyyy-MM-dd").format(dateD);
            if (newDate.equals(date)) {
                System.out.println(this.getName() + "成功了! " +
                        "日期:" + date + " 转换成了:" + newDate);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 获取SimpleDateFormat
     * @param dateFormat 日期格式
     * @return SimpleDateFormat
     */
    public static SimpleDateFormat getSimpleDateFormat(String dateFormat) {
        SimpleDateFormat sdf = THREAD_LOCAL.get();
        if (sdf == null) {
            sdf = new SimpleDateFormat(dateFormat);
            THREAD_LOCAL.set(sdf);
        }
        // 回收
        THREAD_LOCAL.remove();
        return sdf;
    }
}

启动类和解决方案1的启动类代码一样,只需把调用线程改一下就行了。

在这里插入图片描述
可以看到执行结果也都成功了

解决方案3:使用DateTimeFormatter处理(JDK1.8)

在JDK1.8只后出来了DateTimeFormatter,和SimpleDateFormat不同的是,DateTimeFormatter是线程安全的,他是不变对象,可以只创建一个实例,到处引用。

public class SolveTest3 extends Thread{

    private final String date;
    /**
     * 定义DateTimeFormatter格式
     */
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public SolveTest3(String date) {
        super();
        this.date = date;
    }
    @Override
    public void run() {
        // 转换
        LocalDate localDate = LocalDate.parse(date, DATE_TIME_FORMATTER);
        // 打印
        String newDate = localDate.format(DATE_TIME_FORMATTER);
        if (newDate.equals(date)) {
            System.out.println(this.getName() + "成功了! " +
                    "日期:" + date + " 转换成了:" + newDate);
        }
    }
}

启动类同前面方案的一样,只需把调用线程改一下。输出可以看到都成功了

在这里插入图片描述

PS:把LocalDate 转换为 Date如下:

/**
* atStartOfDay():
* atZone():
* toInstant():
*/
Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());

总结:以上总结了三种方式解决SimpleDateFormat转换日期在多线程场景情况下存在的异常问题。但他们也有缺点~~ 方法一创建了多个SimpleDateFormat类的实例比较占用内存,效率较低;方法二使用ThreadLocal不会自动释放,如果不手动remove()容易造成内存泄露;个人建议使用方法三DateTimeFormatter处理。

完…

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

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

相关文章

Camera | 2.MIPI、CSI基础

瑞芯微专栏 上一篇我们讲解了camera的一些基础概念和知识。 我们说了&#xff0c;现在的手机由于高分辨率的要求&#xff0c;现在基本上都是基于MIPI、CSI协议来实现的&#xff0c; 本篇讲解MIPI、CSI的一些基础知识。 摄像头常用术语 下面这些术语是camera驱动中经常用到的…

9.6 容器适配器

文章目录定义一个适配器stack队列适配器queuepriority_queue 优先队列适配器是标准库的一个通用概念&#xff0c;容器、迭代器和函数等都有适配器。适配器是一种机制&#xff0c;接受一种已有容器类型。标准库有三个顺序容器适配器&#xff1a;stack&#xff0c;queue和priorit…

Elasticsearch:关于在 Python 中使用 Elasticsearch 你需要知道的一切 - 8.x

在本文中&#xff0c;我们将讨论如何在 Python 中使用 Elasticsearch。 如果你还不了解 Elasticsearch&#xff0c;可以阅读这篇文章 “Elasticsearch 简介” 进行快速介绍。在我之前的文章 “Elasticsearch&#xff1a;使用最新的 Python client 8.0 来创建索引并搜索”&#…

Pytorch自定义数据集模型训练流程

文章目录Pytorch模型自定义数据集训练流程1、任务描述2、导入各种需要用到的包3、分割数据集4、将数据转成pytorch标准的DataLoader输入格式5、导入预训练模型&#xff0c;并修改分类层6、开始模型训练7、利用训好的模型做预测Pytorch模型自定义数据集训练流程 我们以kaggle竞…

响应式流的核心机制——背压机制

一、响应式流是什么&#xff1f; Reactive Streams 是 2013 年底由 Netflix、Lightbend 和 Pivotal&#xff08;Spring 背后的公司&#xff09;的工程师发起的一项计划&#xff0c;响应式流旨在为无阻塞异步流处理提供一个标准。它旨在解决处理元素流的问题——如何将元素流从…

【BP靶场portswigger-客户端14】点击劫持-5个实验(全)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

Fastdfs分布式文件系统原理浅析

文章目录1、fastdfs文件系统原理简述2、storage server状态2.1 组内新增加一台storage server A时&#xff0c;由系统自动完成已有数据同步&#xff0c;处理逻辑如下&#xff1a;第一步&#xff1a;第二步&#xff1a;第三步&#xff1a;第四步&#xff1a;3、同步时间管理4、B…

[有人@你]请查收你的年终总结报告

嗨&#xff0c;兄dei&#xff0c;我是建模助手。 新年伊始&#xff0c;最近大家想必已经被各大平台的2022年度报告刷屏了。 听歌软件伴你度过的失眠夜&#xff0c;外卖软件拯救你的饥饿时刻&#xff0c;还有某俩宝账单告诉你&#xff0c;其实你是有钱的&#xff0c;只是你看不到…

基于有向图的邻接矩阵计算其割点、割边、压缩图,并用networkx可视化绘制

基于有向图的邻接矩阵计算其割点、割边、压缩图&#xff0c;并用networkx可视化绘制为什么基于邻接矩阵计算图的割点、割边、压缩图实现python代码代码运行效果结论&#xff1a;为什么基于邻接矩阵计算图的割点、割边、压缩图 由于矩阵计算过程&#xff0c;被广泛优化&#xf…

Linux关于 gdb 调试器的使用

坚持看完&#xff0c;结尾有思维导图总结 这里写目录标题debug 和 release 版本gdb 常见命令断点逐行调试和观察变量总结debug 和 release 版本 首先要说的是 &#xff0c;在 Linux 中 gcc 直接编译是不能进行调试的 而是要在加上 -g 选项才能得到可调试的文件 以下程序用一个…

算法第十二期——BFS-双向广搜

双向广搜 应用场景&#xff1a;有确定的起点s和终点t&#xff1b;把从起点到终点的单向搜索&#xff0c;变换为分别从起点出发和从终点出发的“相遇”问题。操作&#xff1a;从起点s(正向搜索&#xff09;和终点t(逆向搜索&#xff09;同时开始搜索&#xff0c;当两个搜索产生…

Spring入门-Spring事务管理

文章目录1&#xff0c;Spring事务管理1.1 Spring事务简介1.1.1 相关概念介绍1.1.2 转账案例-需求分析1.1.3 转账案例-环境搭建步骤1:准备数据库表步骤2:创建项目导入jar包步骤3:根据表创建模型类步骤4:创建Dao接口步骤5:创建Service接口和实现类步骤6:添加jdbc.properties文件步…

数据治理与档案信息资源体系建设

如果要评选大数据或者数字化转型领域中哪个词最让人费解、最讲不清楚&#xff0c;“数据治理&#xff08;Data Governance&#xff09;”绝对是候选之一。说实话&#xff0c;笔者到现在也没有完全整明白&#xff0c;因为数据治理包含的范围太广了&#xff0c;可以说是包罗万象&…

高潜人才的自我要求

前言&#xff0c;上次写了个《潜力出众的你有这样的特质吗&#xff1f;》&#xff0c;地址如下&#xff1a;点我查看&#xff0c;这次在写个高潜人才的自我要求。本次以6个纬度来进行分析&#xff1b;3是基本要求&#xff0c;4是追求卓越&#xff0c;看你目前做到了哪个级别&am…

跨平台API对接(Python)的使用

Jenkins 是一个开源的、提供友好操作界面的持续集成(CI)工具&#xff0c;起源于 Hudson&#xff08;Hudson 是商用的&#xff09;&#xff0c;主要用于持续、自动的构建/测试软件项目、监控外部任务的运行。后端可以利用 Jenkins 对任务进行调度运行&#xff1a;后端可利用 HTT…

【进阶】Spring更简单的读取和存储对象

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录一、存储Bean对象一&#xff09;前置工作&#xff1a;配置扫描路径&#xff08;重要&#xff09;二&#xff09;添加注解存储Bean对象3. 五大类注解&#xff1a;4. 方法注解&#xff1a;6. 相关问题7. 补充【结论、查…

ROS2机器人编程简述humble-第二章-DEVELOPING THE FIRST NODE .2

0.1ROS2机器人编程简述新书推荐-A Concise Introduction to Robot Programming with ROS21.1ROS2机器人编程简述humble-第一章-Introduction2.1ROS2机器人编程简述humble-第二章-First Steps with ROS2 .12.2主要内容是全手工创建一个最简单的自定义节点&#xff0c;其实没啥具…

IB学生必看的时间表(二)

上期谈到在IB预科课程的第一个学年下学期&#xff0c;便要开始作报读大学的准备&#xff0c;到底为什么&#xff1f; 暑假不容松懈 现在来到放暑假了。虽说不用上课&#xff0c;学生没有了学习压力&#xff0c;但就以下三方面来看&#xff0c;学生还是要继续投放心力。 首先&am…

Unity 之 Addressable可寻址系统 -- 代码加载介绍 -- 进阶(一)

Unity 之 可寻址系统 -- 代码加载介绍 -- 进阶&#xff08;一&#xff09;一&#xff0c;可寻址系统代码加载1.1 回调形式1.2 异步等待1.3 面板赋值1.4 同步加载二&#xff0c;可寻址系统分标签加载2.1 场景搭建2.2 代码示例2.3 效果展示三&#xff0c;代码加载可寻址的解释概述…

Cadence OrCAD: 跨页符和电源符号命名优先级的一个小问题

Cadence OrCAD: 跨页符和电源符号命名优先级的一个小问题 遇到的问题 最近项目中&#xff0c;有个电源需要做负载端的反馈&#xff0c;类似下图的signal1和signal1N&#xff0c;反馈使用类似伪差分线&#xff0c;把电压信号和负载端的GND都连到DC-DC控制器。DC-DC对应的反馈引…