SimpleDateFormat学习使用

提起SimpleDateFormat类,想必做过Java开发的童鞋都不会感到陌生。没错,它就是Java中提供的日期时间的转化类。这里,
为什么说SimpleDateFormat类有线程安全问题呢?有些小伙伴可能会提出疑问:我们生产环境上一直在使用SimpleDateFormat
类来解析和格式化日期和时间类型的数据,一直都没有问题啊!我的回答是:没错,那是因为你们的系统达不到
SimpleDateFormat类出现问题的并发量,也就是说你们的系统没啥负载!

接下来,我们就一起看下在高并发下SimpleDateFormat类为何会出现安全问题,以及如何解决SimpleDateFormat类的安全问
题。

重现SimpleDateFormat线程安全问题

public class SimpleDateFormatTest01 {//执行总次数private static final int EXECUTE_COUNT = 1000;//同时运行的线程数量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("2020-01-01");} catch (ParseException e) {System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");e.printStackTrace();System.exit(1);} catch (NumberFormatException e) {System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");e.printStackTrace();System.exit(1);}semaphore.release();} catch (InterruptedException e) {System.out.println("信号量发生错误");e.printStackTrace();System.exit(1);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();System.out.println("所有线程格式化日期成功");}
}

运行结果

java.lang.NumberFormatException: For input string: ""
线程:pool-1-thread-1 格式化日期失败at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)at java.lang.Long.parseLong(Long.java:601)at java.lang.Long.parseLong(Long.java:631)at java.text.DigitList.getLong(DigitList.java:195)at java.text.DecimalFormat.parse(DecimalFormat.java:2051)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 com.xysd.concurrent.lab06.SimpleDateFormatTest01.lambda$main$0(SimpleDateFormatTest01.java:72)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)at java.lang.Thread.run(Thread.java:748)

原因

//SimpleDateFormat
public Date parse(String text, ParsePosition pos) {CalendarBuilder calb = new CalendarBuilder();Date parsedDate;try {//TODOparsedDate = calb.establish(calendar).getTime();if (ambiguousYear[0]) {if (parsedDate.before(defaultCenturyStart)) {parsedDate = calb.addYear(100).establish(calendar).getTime();}}}catch (IllegalArgumentException e) {pos.errorIndex = start;pos.index = oldStart;return null;}return parsedDate;}
//CalendarBuilder
//clear()和set()这两个方法执行不是原子操作所以造成线程不安全
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;}//这里线clearcal.clear();for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {for (int index = 0; index <= maxFieldIndex; index++) {if (field[index] == stamp) {//然后再setcal.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;}

解决方案一 方法局部变量

public class SimpleDateFormatTest01 {//执行总次数private static final int EXECUTE_COUNT = 1000;//同时运行的线程数量private static final int THREAD_COUNT = 20;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 simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");simpleDateFormat.parse("2020-01-01");} catch (ParseException e) {System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");e.printStackTrace();System.exit(1);} catch (NumberFormatException e) {System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");e.printStackTrace();System.exit(1);}semaphore.release();} catch (InterruptedException e) {System.out.println("信号量发生错误");e.printStackTrace();System.exit(1);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();System.out.println("所有线程格式化日期成功");}
}

解决方案二 使用synchronized锁

public class SimpleDateFormatTest01 {//执行总次数private static final int EXECUTE_COUNT = 1000;//同时运行的线程数量private static final int THREAD_COUNT = 20;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 {synchronized(simpleDateFormat){simpleDateFormat.parse("2020-01-01");}} catch (ParseException e) {System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");e.printStackTrace();System.exit(1);} catch (NumberFormatException e) {System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");e.printStackTrace();System.exit(1);}semaphore.release();} catch (InterruptedException e) {System.out.println("信号量发生错误");e.printStackTrace();System.exit(1);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();System.out.println("所有线程格式化日期成功");}
}

解决方案三 Lock锁

public class SimpleDateFormatTest01 {//执行总次数private static final int EXECUTE_COUNT = 1000;//同时运行的线程数量private static final int THREAD_COUNT = 20;private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");private static Lock lock = new ReentrackLock();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 {lock.lock();simpleDateFormat.parse("2020-01-01");} catch (ParseException e) {System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");e.printStackTrace();System.exit(1);} catch (NumberFormatException e) {System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");e.printStackTrace();System.exit(1);}finally{lock.unlock();}semaphore.release();} catch (InterruptedException e) {System.out.println("信号量发生错误");e.printStackTrace();System.exit(1);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();System.out.println("所有线程格式化日期成功");}
}

解决方案四 ThreadLocal

public class SimpleDateFormatTest01 {//执行总次数private static final int EXECUTE_COUNT = 1000;//同时运行的线程数量private static final int THREAD_COUNT = 20;//SimpleDateFormat对象private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");//本地线程private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd");}};private static DateFormat getDateFormat() {SimpleDateFormat dateFormat = threadLocal.get();if (dateFormat == null) {dateFormat = new SimpleDateFormat("yyyy-MM-dd");threadLocal.set(dateFormat);}return dateFormat;}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 {getDateFormat().parse("2020-01-01");} catch (ParseException e) {System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");e.printStackTrace();System.exit(1);} catch (NumberFormatException e) {System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");e.printStackTrace();System.exit(1);}semaphore.release();} catch (InterruptedException e) {System.out.println("信号量发生错误");e.printStackTrace();System.exit(1);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();System.out.println("所有线程格式化日期成功");}}

解决方案五 DateTimeFormatter

public class SimpleDateFormatTest01 {//执行总次数private static final int EXECUTE_COUNT = 1000;//同时运行的线程数量private static final int THREAD_COUNT = 20;private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("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 {LocalDate.parse("2020-01-01", formatter);} catch (Exception e) {System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");e.printStackTrace();System.exit(1);}semaphore.release();} catch (InterruptedException e) {System.out.println("信号量发生错误");e.printStackTrace();System.exit(1);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();System.out.println("所有线程格式化日期成功");}
}

总结

在高并发情况下推荐使用ThreadLocal或DateTimeFormatter

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

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

相关文章

【mongoDB】集合的创建和删除

目录 1.集合的创建 2. 查看所有集合 3.删除集合 1.集合的创建 格式&#xff1a; db.createCollection ( name ) 例如创建一个名为 bbb 的集合 还可以通过传递一个选项对象来指定集合的属性&#xff0c;例如最大文档的大小&#xff0c;索引选项等 例如 这样创建了一个名为 cc…

Linux CentOS使用Docker搭建laravel项目环境(实践案例详细说明)

一、安装docker # 1、更新系统软件包&#xff1a; sudo yum update# 2、安装Docker依赖包 sudo yum install -y yum-utils device-mapper-persistent-data lvm2# 3、添加Docker的yum源&#xff1a; sudo yum-config-manager --add-repo https://download.docker.com/linux/cen…

如何在IntelliJ IDEA数据库控制台操作Redis

如何在IntelliJ IDEA数据库控制台操作Redis TIPS 本文理论支持IntelliJ IDEA家族所有IDE&#xff08;例如Data Grip等&#xff09;、所有版本理论支持所有基于JDBC的各种GUI工具&#xff01; 最近工作中&#xff0c;经常要操作到Redis&#xff0c;尽管市面上的Redis客户端GUI非…

kotlin 项目中文件显示带.kt 结尾与不带.kt结尾

kotlin 项目中文件显示带.kt 结尾与不带.kt结尾&#xff0c;而且显示的图片也不一样&#xff0c; 首先说下&#xff0c;这个只是开发工具上的一个设计细节展示&#xff0c;无论显示效果是否带.kt 实际的文件都是带.kt结尾的&#xff0c;这个可以到文件的目录下查看文件 创建文…

算法基础之树状数组

文章目录 树状数组 树状数组 树状数组能解决的最关键的问题就是能够 O ( log ⁡ n ) O(\log n) O(logn)内&#xff0c;给某个位置上的数&#xff0c;加上一个数&#xff0c;或者求前缀和 他和前缀和数组的区别就是&#xff0c;树状数组支持修改原数组的内容&#xff0c;而前缀…

代码随想录算法训练营第30天(回溯算法06 | ● 332.重新安排行程 ● 51. N皇后 ● 37. 解数独 ● 总结

回溯算法06 332.重新安排行程&#xff08;可跳过&#xff09;解题思路难点 51.N皇后&#xff08;可跳过&#xff09;解题思路回溯三部曲难点 5. 解数独&#xff08;可跳过&#xff09;解题思路回溯三部曲 总结篇&#xff08;没来及看 332.重新安排行程&#xff08;可跳过&#…

C语言第九弹---二维数组

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 二维数组 1、二维数组的创建 1.1、二维数组的概念 ​1.2、⼆维数组的创建 2、二维数组的初始化 2.1、不完全初始化 ​2.2、完全初始化 ​2.3、按照行初始化 ​2.4、…

如何使用Docker安装Spug并实现远程访问本地运维管理界面

文章目录 前言1. Docker安装Spug2 . 本地访问测试3. Linux 安装cpolar4. 配置Spug公网访问地址5. 公网远程访问Spug管理界面6. 固定Spug公网地址 前言 Spug 面向中小型企业设计的轻量级无 Agent 的自动化运维平台&#xff0c;整合了主机管理、主机批量执行、主机在线终端、文件…

Vue开发之proxy代理的配置(附带uniapp代理配置)

vue 1.在vue.config.js中添加 devServer 属性中配置 proxy 属性 module.exports {productionSourceMap: false,publicPath: /,devServer: {port: 8085,proxy: {/api/admin: {target: http://10.58.104.70:6111,changeOrigin: true,pathRewrite: {/api/: /}},/api: {target: …

自动 卸载或安装 Python第三方库

在调用 pip.exe 时&#xff0c;可以使用相对路径也可以使用绝对路径 路径中如果包含空格&#xff0c;最好使用相对路径&#xff0c;这就要求 pip.exe 所在文件夹设置为环境变量 可以参考&#xff1a; Windows下将文件夹设置为环境变量 echo off setlocal enabledelayedexpansi…

UE创建数据表格

创建一个数据表格需要行结构 继承自FTableRowBase的一个子类 效果 如何使用它 在蓝图中给C该类型的指针变量选用 UDataTable类型的 FindRow()函数可查询并返回对应行的行结构 FTableRowBase GetAllRows()函数可以获得该数据表的所有行、

centos 安装mysql5.7教程

一&#xff0c;配置yum mysql5.7安装源 配置yum mysql5.7安装源 yum localinstall https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm 配置mysql5.7安装源成功 查看配置成功的安装源 yum repolist enabled | grep "mysql*" 执行后看到已配…

环境监测与预报:探索天气预报查询API在生态保护中的作用

摘要 随着全球气候变化的加剧&#xff0c;生态保护已成为全球关注的焦点。天气预报API作为一种强大的工具&#xff0c;不仅能够提供实时的气象数据&#xff0c;还能在生态保护领域发挥重要作用。本文将探讨天气预报API如何帮助科学家、环保组织和政策制定者更好地理解和预测环…

什么是 Docker

1.什么是 Docker 1.1 官方定义 最新官网首页 # 1.官方介绍 - We have a complete container solution for you - no matter who you are and where you are on your containerization journey. - 翻译: 我们为你提供了一个完整的容器解决方案,不管你是谁,不管你在哪,你都可以…

vue3常用代码

文章目录 监听路由vue3 警告Feature flag __VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not explicitly defined.mitt、project/inject 无效解决方案 菜鸟做项目时发现很多 vue3 常用的代码&#xff0c;所以来总结一下&#xff01; 监听路由 import { useRoute } from "…

如何禁用 el-table 单独某一行,修改某一行样式等(最有效)

案例&#xff1a;根据el-table :data"tableData"中是否有invalidStatus值为1&#xff0c;如果是就是不禁用&#xff0c;否就禁用这一行&#xff0c;当然这个invalidStatus随意就行&#xff0c;只要在tabledata中的每一行数据中有这个属性就行&#xff0c;也就是row中…

Android创建保存Excel文件

Android开发生成保存Excel文件&#xff0c;首先下载两个jar包。下载地址&#xff1a;Android读写Excel文件的两个jar包资源-CSDN文库 poi-3.12-android-a.jar poi-ooxml-schemas-3.12-20150511-a.jar 把jar包放在app的libs文件夹下&#xff0c;引用jar我一般都在build.gradle的…

代码随想录算法训练营第四十一天(动态规划篇)|理论基础,509. 斐波那契数, 70. 爬楼梯, 746. 使用最小花费爬楼梯

动态规划理论基础 动态规划&#xff1a;每一个状态一定是由上一个状态推导出来的。 贪心&#xff1a;局部直接选最优的 解题步骤 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 509. 斐波那契数 题目…

conda-建立多个python环境

1. 安装 下载地址&#xff1a;Miniconda — miniconda documentation 2. 安装好了会自动配置环境变量&#xff0c;如果没有配置手动配置 3. 检查conda环境 4. 设置conda配置文件 在‪C:\Users\Administrator下新建文件【.condarc】 channels: //镜像地址- https://mirrors.…

girhub添加 SSH 密钥

1 打开终端 输入 ssh-keygen -t rsa -b 4096 -C "github邮箱地址"如果不需要密码可以一路回车 出现这个页面就是生存成功了 open ~/.ssh // 打开.ssh 找到id_rsa.pub复制出内容新建ssh密钥输入内容,保存即可