SimpleDateFormat线程不安全解析以及解决方案

news2024/10/7 18:32:26

我们都知道SimpleDateFormat是Java中用来日期格式化的一个类,可以将特定格式的字符转转化成日期,也可将日期转化成特定格式的字符串。比如

  • 将特定的字符串转换成日期
public static void main(String[] args) throws ParseException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        Date format = simpleDateFormat.parse("2022-12-19 11:55:56");
        System.out.println(format);
    }

在这里插入图片描述

  • 将日期转化成特定格式的字符串
public static void main(String[] args) throws ParseException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//        Date format = simpleDateFormat.parse("2022-12-19 11:55:56");
        String format = simpleDateFormat.format(new Date());
        System.out.println(format);
    }

在这里插入图片描述
这种都是在单线程的情况下,这样子是没有问题的,但是如果多线程调用同一个SimpleDateFormat 对象就会出现安全问题。

线程安全问题演示(参考了冰河的文章)

在下面的代码中我们定义了SimpleDateFormat对象被使用的总次数以及同时使用的最大线程的数量。在里面我用到了Semaphore 信号量用来限流,也就是限制线程的最大数量是20个。同时用到了CountDownLatch ,用来保证所有的线程都执行完成之后主线程才能继续往下执行。

package com.dongmu;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * 重现多线程下SimplDateFormat线程不安全的现象
 */
public class SimpleDateFormatTest {
//    定义执行的总次数
    private static final int EXECUTE_COUNT = 2000;
//    同时运行的最大的线程数量
    private static final int THREAD_COUNT = 20;
//    SimpleDateFormat对象
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
    public static void main(String[] args) throws InterruptedException {
        final Semaphore semaphore = new Semaphore(THREAD_COUNT);
        final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0;i<EXECUTE_COUNT;i++){
            executorService.execute(()->{
                try {
                    semaphore.acquire();

                    try {
                        simpleDateFormat.parse("2022-12-19");

                    } catch (ParseException e) {
                        System.out.println("线程"+Thread.currentThread().getName()+"格式化日期失败");
                        throw new RuntimeException(e);
                    }catch (NumberFormatException e){
                        System.out.println("线程"+Thread.currentThread().getName()+"格式化日期失败");
                        e.printStackTrace();
                    }
                    semaphore.release();

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期完成");

    }
}

可以看到执行的结果如下:
在这里插入图片描述
这里报出了一个异常class NumberFormatException,这就是由于多线程使用同一个SimpleDateFormat对象导致的。

具体的原因是我们在调用parse方法的时候

public Date parse(String source) throws ParseException
    {
        ParsePosition pos = new ParsePosition(0);
        Date result = parse(source, pos);
        if (pos.index == 0)
            throw new ParseException("Unparseable date: \"" + source + "\"" ,
                pos.errorIndex);
        return result;
    }

而上面的携带两个参数的parse方法的最后调用了

try {
            parsedDate = calb.establish(calendar).getTime();
            // If the year value is ambiguous,
            // then the two-digit year == the default start year
            if (ambiguousYear[0]) {
                if (parsedDate.before(defaultCenturyStart)) {
                    parsedDate = calb.addYear(100).establish(calendar).getTime();
                }
            }
        }

这个establish方法接收了一个参数calendar,而这个对象是一个SimpleDateFormat对象的遍历,多线程使用同一个这个对象,而在这个establish方法中对calendar进行了clearset的操作。这就导致了calendar对象内部的数据在多线程的操作下混乱了,也就导致在进行数据格式化的时候出现了原本不应该出现的数字,也就导致了NumberFormatException

 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;
    }

同样在format中也存在同样的问题,也可能出现异常。

解决方案

  • 第一种就是直接每次使用的时候都new一个新SimpleDateFormat的对象。
  • 第二种就是在使用的时候用synchronized修饰。
  • 第三种就是
//Lock对象
private static Lock lock = new ReentrantLock();
在simpleDateFormat.parse("2022-12-19");的前面使用lock.lock();然后加上finally语句块,在这个语句快钟进行lock.unlock();
注意这里一定要在finally语句块钟进行执行释放锁的操作,避免因为程序异常导致锁无法是释放的问题。
  • 第四种方案,使用ThreadLocal保证每一个线程都拥有自己的simpleDateFormat变量,这样就不会出现线程安全问题了。
//定义成员变量
 private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };
//然后将原来parse的部分该成
threadLocal.get().parse("2022-12-19");
  • 第五种方案,使用DateTimeFormatter类,这是Java8提供的新的如期时间Api中的类,这是个线程安全的类,可以在高并发环境下直接使用。
private static DateTimeFormatter dateTimeFormatter =DateTimeFormatter.ofPattern("yyyy-MM-dd");

dateTimeFormatter.parse("2022-12-19");
  • 第六种方式使用.joda-time方式,这种方式需要引入依赖,这里不做过多介绍,上面的方法已经够用了。

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

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

相关文章

windows11打开隐藏的gpedit.msc本地组策略编辑器以及禁止自动更新系统

目录打开隐藏的gpedit.msc本地组策略编辑器windows11禁止自动更新系统打开隐藏的gpedit.msc本地组策略编辑器 1.新建.txt文本文件&#xff0c;输入如下代码 echo off pushd “%~dp0” dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtension…

a股行情接口功能特点

a股行情接口功能特点&#xff0c;如下&#xff1a; 1、支持A股历史交易查询&#xff1b; 2、包括股票数据&#xff1b; 3、每日A股收盘后更新当日交易数据&#xff0c;停牌不更新&#xff1b; 4、支持多股历史数据一次查询&#xff1b; 5、支持任何时间段查询&#xff1b;…

Linux进程概念(二)

Linux进程概念进程状态普通操作系统层面理解运行与阻塞挂起与阻塞Linux是怎么做的孤儿进程进程优先级什么是优先级如何改变优先级其他概念进程状态 进程状态分有&#xff1a; 运行 新建 就绪 挂起 阻塞 等待 停止 挂机 死亡… 状态其实就是返回的整形&#xff0c;就像某个数字…

【毕业设计_课程设计】基于 SVM 分类器的动作识别系统(源码+论文)

文章目录0 项目说明1 研究目的2 研究方法2.1 动作采集2.2 动作识别2.3 智能家居模拟3 论文目录4 项目工程0 项目说明 基于 SVM 分类器的动作识别系统 提示&#xff1a;适合用于课程设计或毕业设计&#xff0c;工作量达标&#xff0c;源码开放 1 研究目的 本项目对经典 SVM 二…

从企业关心的重点,带你了解商业智能BI

企业进行信息化建设&#xff0c;能通过业务信息系统以及规范化、标准化的业务流程存储高质量的业务数据&#xff0c;而这些数据则是数字化转型成功的重要条件。只有信息化和数字化共同发展&#xff0c;企业才能成功完成数字化转型&#xff0c;构建全新的商业模式&#xff0c;完…

kafka的客户端限流(资源配额)

前言 本文说明的是Kafka的客户端&#xff08;生产者、消费者&#xff09;与broker之前的限流&#xff0c;不是kafka的broker间topic副本同步的限流。 客户端限流 在kafka的官方文档&#xff0c;不叫限流&#xff0c;叫做资源配额&#xff1a;通过对客户端请求进行配额&#…

细节决定成败:探究Mybatis中javaType和ofType的区别

一. 背景描述 今天&#xff0c;给学生讲解了Mybatis框架&#xff0c;学习了基础的ORM框架操作及多对一的查询。在练习的时候&#xff0c;小张同学突然举手求助&#xff0c;说在做预习作业使用一对多查询时&#xff0c;遇到了ReflectionException 异常 。 二. 情景再现 1. 实…

css 网格布局

简介&#xff1a; 网格是由一系列水平及垂直的线构成的一种布局模式。一个网格通常具有许多的列&#xff08;column&#xff09;与行&#xff08;row&#xff09;&#xff0c;以及行与行、列与列之间的间隙&#xff0c;这个间隙一般被称为沟槽&#xff08;gutter&#xff09;。…

智能摄像头视频监控,智和信通一站式解决方案

为进一步加强公共安全视频监控建设联网应用工作&#xff0c;推动整合各类视频图像资源&#xff0c;九部委联合发布的《关于加强公安视频监控建设网络化应用的若干意见》&#xff0c;明确以全域覆盖、全网共享、全时可用、全程可控为总目标。在大大加强城市社会安全保障能力的同…

Kafka Producer 开发

Kafka Producer 开发 kafka包含5个核心的API接口定义&#xff1a; Producer API - 允许应用程序往kafka集群中的topic中发送事件消息Consumer API - 允许应用程序从kafka topic 中读取数据Streams API - 允许对输入数据流进行数据计算、转换&#xff0c;并发送到其他主题进行…

Ultra-high Resolution Image Segmentation via Locality-aware Context Fusion

极高图像语义分割。 作者使用了一个高分辨率分割的pipeline&#xff0c;将原始的超高分辨率图像分成一块一块的用于局部分割&#xff0c;然后将局部的分割结果融合形成最终的高分辨率分割。 方法&#xff1a;1&#xff1a;作者引入了一个局部感知上下文融合&#xff08;LCF&…

怎么提升360网站权重?怎么查询网站在360权重

怎么提升360网站权重&#xff1f; 一、增加网站流量 1、做高指数的关键词排名。 2、关键词的合理布局。 3、关键词的布局必须注意密度。 4、网站关键词的页面布局必须合理。二、网站页面内容布局 网站页面的内容可以说是网站的灵魂。网站的好坏完全取决于网站的内容是否能给访问…

Redis6新数据类型Bitmaps

Redis6新数据类型1.Bitmaps2.命令1.Bitmaps 简介&#xff1a;现代计算机用二进制(位)作为信息的基础单位&#xff0c;1个字节等于8位&#xff0c;例如“abc”字符串由3个字节组成&#xff0c;但实际在计算机存储时将其用二进制表示&#xff0c;“abc”分别对应的ASCII码是97、…

​草莓熊python turtle绘图(圣诞元旦倒数雪花版)附源代码

​草莓熊python turtle绘图&#xff08;圣诞元旦倒数雪花版&#xff09;附源代码 本篇目录&#xff1a; 一、前言 二、​草莓熊python绘图&#xff08;圣诞元旦倒数雪花版&#xff09;效果图 三、源代码保存方法 四、代码命令解释 &#xff08;1&#xff09;、绘图基本代码…

LaTeX教程(四)——文档内元素

文章目录1. 表格2. 插入图片3. 盒子4. 浮动体1. 表格 LaTeX的表格不想Word能够做到所见即所得&#xff0c;当表格较小还好&#xff0c;一旦表格内容逐渐增多&#xff0c;那么编写表格就变得十分麻烦了&#xff0c;为此&#xff0c;一般都是用在线表格并生成LaTeX代码的形式来得…

Linux——管道和重定向

一、Linux的文件 linux中奉行一切皆文件&#xff0c;包括目录、链接&#xff08;类似windows的快捷方式&#xff09;、设备文件。 在内核中,所有打开的文件都使用文件描述符&#xff08;一个非负整数&#xff09;标记。文件描述符的变化范围是0~OPEN_MAX – 1。早期的unix系统…

前端CDN和DNS

DNS的基础知识 统一资源定位符(URL) scheme: 方案&#xff0c;包括http&#xff0c;https协议。 host&#xff1a;主机 port&#xff1a;端口 path&#xff1a;路径 query&#xff1a;查询 fragment&#xff1a;片段&#xff0c;访问网址时候定位某个位置 DNS &#xff08;Do…

Java 开发环境配置

在本章节中我们将为大家介绍如何搭建Java开发环境。 Windows 上安装开发环境Linux 上安装开发环境安装 Eclipse 运行 Javawindow系统安装java 下载JDK 首先我们需要下载 java 开发工具包 JDK&#xff0c;下载地址&#xff1a;Java Downloads | Oracle&#xff0c;在下载页面…

Kaggle房价预测 特征工程模型聚合

目录 一&#xff1a;Kaggle数据集准备 二&#xff1a;数据集分析 三&#xff1a;空值处理 四&#xff1a;空值填充 五&#xff1a;查找所有字符列 六&#xff1a;实例化独热编码对象 七&#xff1a;方差过滤 八&#xff1a;特征数据提取 九&#xff1a;查看特征之间…

跨域/解决跨域方法

一、同源策略 同源策略(Same Origin Policy)是一种约定&#xff0c;它是浏览器最核心也是最基本的安全功能。同源策略会阻止一个域的javascrip脚本和另一个域的内容进行交互&#xff0c;是用于隔离潜在恶意文件的关键安全机制&#xff1b;关于这一点我们后面会举例说明。如果缺…