SimpleDateFormat两个著名的坑

2024-03-16 10:40

本文主要是介绍SimpleDateFormat两个著名的坑,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 坑1:定义的static的SimpleDateFormat 可能出现线程安全问题
    • 坑2:当需要解析的字符串和格式不匹配的时候,SimpleDateFormat 并不报错,而是返回其他日期

坑1:定义的static的SimpleDateFormat 可能出现线程安全问题

SimpleDateFormat是线程不安全的类,定义为static对象,会有数据同步风险。通过源码可以看出,SimpleDateFormat内部有一个Calendar对象,在日期转字符串或字符串转日期的过程中,多线程共享时有非常高的概率产生错误,推荐的方式之一时使用ThreadLocal,让每个线程单独拥有这个对象。

示例代码:

public class SimpleDateFormatterTest {static ExecutorService threadPool = Executors.newFixedThreadPool(20);static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) {for(int i=0;i<200;i++){threadPool.execute(()->{try {System.out.println(format.parse("2021-07-09 16:29:21"));} catch (ParseException e) {e.printStackTrace();};});}}
}

运行程序后大量报错,且没有报错的输出结果也不正确

在这里插入图片描述
在这里插入图片描述
为什么会出现上述问题呢?
SimpleDateFormat 的作用是的另一解析和格式化日期时间的模式,这看起来是一次性工作。应该复用,但它的解析和格式化的操作是非线程安全的。

*SimpleDateFormat 继承 DateFormat ,DateFormat 有一个成员变量 calendar。

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

最终会调用CalendarBuilder的establish 方法来构建Calendar

parsedDate = calb.establish(calendar).getTime();

establish 方法内部是先清空 Calendar再构建Calendar,整个的操作没有加锁

 Calendar establish(Calendar cal) {......cal.clear();......return cal;}```如果多线程在并发操作一个Calendar, 可能会产生一个线程还没来得及处理Calendar 就被另外一个线程清空了,所以会出现解析错误和异常。那么怎么解决呢?* 每次使用时new一个SimpleDateFormat 的 实例,这样可以保证每个实例使用自己的Calendar实例,但是每次使用都需要new一个对象,并且使用后由于没有其他引用,又需要回收,开销会很大。
* 可以使用syncheronized 对SimpleDtaFormat实例进行同步
* 【推荐】 使用ThreadLocl,这样每个线程只需要使用一个SimpleDateFormate实例,这相比第一种方式 节省了对象的创建销毁开销,并且不需要使多个线程同步。
* 使用Java8的DateTimeFormatter 类下面是用ThreadLocal实现的示例:
```java
public class SimpleDateFormatterTest {static ExecutorService threadPool = Executors.newFixedThreadPool(20);private static  final ThreadLocal<SimpleDateFormat> SIMPLEDATEFORMAT_THREADLOCAL = new ThreadLocal<SimpleDateFormat>(){@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");}};static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static void main(String[] args) throws InterruptedException {for(int i=0;i<200;i++){threadPool.execute(()->{try {System.out.println(SIMPLEDATEFORMAT_THREADLOCAL.get().parse("2021-07-09 16:29:21"));} catch (ParseException e) {e.printStackTrace();};});}threadPool.shutdown();threadPool.awaitTermination(10, TimeUnit.SECONDS);}
}

坑2:当需要解析的字符串和格式不匹配的时候,SimpleDateFormat 并不报错,而是返回其他日期

    public static void main(String[] args) throws InterruptedException, ParseException {String dateString = "20210908";SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMM");System.out.println(simpleDateFormat.parse(dateString));}

结果:
Wed Aug 01 00:00:00 CST 2096

竟然输出了 2096年了

对于上面的两个坑,我们可以使用java8的DateTimeFormatter 来避免

1) 解决第一个坑

public class DateTimeFormatterTest {static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");static ExecutorService threadPool = Executors.newFixedThreadPool(20);public static void main(String[] args) throws InterruptedException {for(int i=0;i<200;i++){threadPool.execute(()->{System.out.println(dateTimeFormatter.parse("2021-07-09 16:29:21"));});}threadPool.shutdown();threadPool.awaitTermination(10, TimeUnit.SECONDS);}}
  1. 解决第二个坑
 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM");System.out.println(dateTimeFormatter.parse("2021-07-09 16:29:21"));

此时会直接报错,而不会出现不正确的结果;

DateTimeFormatter 是线程安全的,可以定义为static 使用,最后,DateTimeFormatter 的解析比较严格,需要解析的字符串和格式不匹配时,会直接报错。而不是错误的解析。

这篇关于SimpleDateFormat两个著名的坑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/815211

相关文章

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

2024年AMC10美国数学竞赛倒计时两个月:吃透1250道真题和知识点(持续)

根据通知,2024年AMC10美国数学竞赛的报名还有两周,正式比赛还有两个月就要开始了。计划参赛的孩子们要记好时间,认真备考,最后冲刺再提高成绩。 那么如何备考2024年AMC10美国数学竞赛呢?做真题,吃透真题和背后的知识点是备考AMC8、AMC10有效的方法之一。通过做真题,可以帮助孩子找到真实竞赛的感觉,而且更加贴近比赛的内容,可以通过真题查漏补缺,更有针对性的补齐知识的短板。

两个长数字相加

1.编程题目 题目:要实现两个百位长的数字直接相加 分析:因为数字太长所以无法直接相加,所以采用按位相加,然后组装的方式。(注意进位) 2.编程实现 package com.sino.daily.code_2019_6_29;import org.apache.commons.lang3.StringUtils;/*** create by 2019-06-29 19:03** @autho

创建一个大的DIV,里面的包含两个DIV是可以自由移动

创建一个大的DIV,里面的包含两个DIV是可以自由移动 <body>         <div style="position: relative; background:#DDF8CF;line-height: 50px"> <div style="text-align: center; width: 100%;padding-top: 0px;"><h3>定&nbsp;位&nbsp;

在二叉树中找到两个节点的最近公共祖先(基于Java)

如题  题解 public int lowestCommonAncestor(TreeNode root, int o1, int o2) {//记录遍历到的每个节点的父节点。Map<Integer, Integer> parent = new HashMap<>();Queue<TreeNode> queue = new LinkedList<>();parent.put(roo

Java中计算两个日期间隔多少天

String dbtime1 = "2017-02-23";  //第二个日期 String dbtime2 = "2017-02-22";  //第一个日期 //算两个日期间隔多少天 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); Date date1 = format.parse(dbtime1); Date dat

Java利用正则表达式获取指定两个字符串之间的内容

package com.starit.analyse.util;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.List;import java.util.regex.Matcher;import java.util.regex.Pattern;public class DealSt

git如何灵活切换本地账号对应远程github的两个账号

git如何灵活切换本地账号对应远程github的两个账号 问题: 有时候我们会同时维护两个github的账号里面的仓库内容,这时候本地git需要频繁的切换ssh,以方便灵活的与两个账号的仓库可以通信。这篇日记将阐述我是怎么解决这个问题的。1. 第一个账户 生成本地SSH2. 注意 我们要设置第二个账户的 本地 SSH 时3. 两个账号来回切换 问题: 有时候我们会同时维护两个git

交换两个变量数值的3种方法

前言:交换两个数值可不是"a = b,b = a"。这样做的话,a先等于了b的值;当“b = a”后,因为此时a已经等于b的值了,这个语句就相当于执行了b = b。最终的数值关系就成了a == b,b == b。 下面教给大家3种交换变量数值的方法: 目录 1. 中介法 2. 消和法 3. 异或法 4. 总结 1. 中介法 中介法(又称 临时变量法 或 酱油法),其中心

用异或交换两个整数的陷阱

前面我们谈到了,可用通过异或运算交换两个数,而不需要任何的中间变量。 如下面: void exchange(int &a, int &b) {     a ^= b;     b ^= a;     a ^= b; } 然而,这里面却存在着一个非常隐蔽的陷阱。 通常我们在对数组进行操作的时候,会交换数组中的两个元素,如exchang(&a[i], &b[j]),