SimpleDateFormat定义

SimpleDateFormat 是一个以与语言环境有关的方式来格式化和解析日期的具体类。它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。

SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。但是,仍然建议通过 DateFormat 中的 getTimeInstance、getDateInstance 或 getDateTimeInstance 
来创建日期-时间格式器。每一个这样的类方法都能够返回一个以默认格式模式初始化的日期/时间格式器。可以根据需要使用 applyPattern 方法来修改格式模式。

官网同步建议

同步
日期格式是不同步的。建议为每个线程创建独立的格式实例。如果多个线程同时访问一个格式,则它必须是外部同步的。

为什么线程不安全

上图中,SimpleDateFormat类中,有个对象calendar

calendar 
          DateFormat 使用 calendar 来生成实现日期和时间格式化所需的时间字段值。

当SimpleDateFormat用static申明,多个线程共享SimpleDateFormat对象是,也共享该对象的calendar对象。而当调用parse方法时,会clear所有日历字段和值。当线程A正在调用parse,线程B调用clear,这样解析后的数据就会出现偏差

//parse方法
@Override
public Date parse(String text, ParsePosition pos)
{
try {
parsedDate = calb.establish(calendar).getTime();
...
}
}

//establish方法
Calendar establish(Calendar cal) {
...
//将此 Calendar 的所有日历字段值和时间值(从历元至现在的毫秒偏移量)设置成未定义
cal.clear();
}

同样 formart中也用到了calendar对象,将date设置到日历的时间字段中

 private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
...
}

当线程A调用setTime,而线程B也调用setTime,这时候线程A最后得到的时间是 最后线程B的时间。也会导致数据偏差

不安全示例:

public static void main(String[] args) throws InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = "1111-11-11 11:11:11";
ExecutorService executorService = Executors.newFixedThreadPool();
for(int i=;i<;i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
//多个线程操作同一个sdf对象
System.out.println(sdf.format(sdf.parse(dateStr)) + "---" + Thread.currentThread().getName());
} catch (ParseException e) {
System.out.println("--------------> error, " + e.getMessage());
}
}
}); }
executorService.shutdown();
}

执行结果:

...
-- ::---pool--thread-
0011-- ::---pool--thread-
0011-- ::---pool--thread-
-- ::---pool--thread-
-- ::---pool--thread-
-- ::---pool--thread-
-- ::---pool--thread-
-- 00::---pool--thread-
-- ::---pool--thread-
...

可以看到数据出现偏差

解决方案

1.为每个实例创建一个单独的SimpleDateFormat对象

public static void main(String[] args) throws InterruptedException {
//SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = "1111-11-11 11:11:11";
ExecutorService executorService = Executors.newFixedThreadPool();
for(int i=;i<;i++) {
executorService.submit(new Runnable() {
//为每个线程创建自己的sdf对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void run() {
try {
System.out.println(sdf.format(sdf.parse(dateStr)) + "---" + Thread.currentThread().getName());
} catch (ParseException e) {
System.out.println("--------------> error, " + e.getMessage());
}
}
}); }
executorService.shutdown();
}

  缺点:每次new一个实例,都会new一个format对象,虚拟机内存消耗大,垃圾回收频繁

2.给静态SimpleDateFormat对象加锁,使用Lock或者synchronized修饰

public static void main(String[] args) throws InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = "1111-11-11 11:11:11";
ExecutorService executorService = Executors.newFixedThreadPool();
for(int i=;i<;i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
//加同步锁
synchronized (sdf) {
try {
System.out.println(sdf.format(sdf.parse(dateStr)) + "---" + Thread.currentThread().getName());
} catch (ParseException e) {
System.out.println("--------------> error, " + e.getMessage());
}
}
}
}); }
executorService.shutdown();
}

  缺点:性能差,其他线程要等待锁释放

3.使用ThreadLocal为每个线程创建一个SimpleDateFormat对象副本,有线程隔离性,各自的副本对象也不会被其他线程影响

public static void main(String[] args) throws InterruptedException {
//SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//初始化threadLocal并设置值
ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
String dateStr = "1111-11-11 11:11:11";
ExecutorService executorService = Executors.newFixedThreadPool(100);
for(int i=0;i<100;i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println(threadLocal.get().format(threadLocal.get().parse(dateStr)) + "---" + Thread.currentThread().getName());
} catch (ParseException e) {
System.out.println("--------------> error, " + e.getMessage());
}
}
}); }
executorService.shutdown();
        //清理threadLocal,生产环境不清理容易导致内存溢出
threadLocal.remove();
}

ThreadLocal原理分析

SimpleDateFormat 线程不安全及解决方案的更多相关文章

  1. SimpleDateFormat线程不安全的5种解决方案!

    1.什么是线程不安全? 线程不安全也叫非线程安全,是指多线程执行中,程序的执行结果和预期的结果不符的情况就叫做线程不安全. ​ 线程不安全的代码 SimpleDateFormat 就是一个典型的线程不 ...

  2. SimpleDateFormat线程不安全原因及解决方案

    一. 线程不安全验证: /** * SimpleDateFormat线程安全测试 * 〈功能详细描述〉 * * @author 17090889 * @see [相关类/方法](可选) * @sinc ...

  3. SimpleDateFormat线程不安全及解决办法

    原文链接:https://blog.csdn.net/csdn_ds/article/details/72984646 以前没有注意到SimpleDateFormat线程不安全的问题,写时间工具类,一 ...

  4. SimpleDateFormat线程不安全及解决办法(转)

    以前没有注意到SimpleDateFormat线程不安全的问题,写时间工具类,一般写成静态的成员变量,不知,此种写法的危险性!在此讨论一下SimpleDateFormat线程不安全问题,以及解决方法. ...

  5. SimpleDateFormat 线程安全的解决方案--DateTimeFormatter

    SimpleDateFormat并不是线程安全的,因为在SimpleDateFormat中持有一个Calendar类对象在Parse 和Format方法时会调用calendar.setTime(dat ...

  6. SimpleDateFormat线程不安全问题解决及替换方法

    场景:在多线程情况下为避免多次创建SimpleDateForma实力占用资源,将SimpleDateForma对象设置为static. 出现错误:SimpleDateFormat定义为静态变量,那么多 ...

  7. SimpleDateFormat线程安全问题排查

    一. 问题现象 运营部门反馈使用小程序配置的拉新现金红包活动二维码,在扫码后跳转至404页面. 二. 原因排查 首先,检查扫码后的跳转链接地址不是对应二维码的实际URL,根据代码逻辑推测,可能是acc ...

  8. SimpleDateFormat线程不安全问题处理

    在工作中,通过SimpleDateFormat将字符串类型转为日期类型时,发现有时返回的日期类型出错,调用方法如下: public final class DateUtil { static fina ...

  9. SimpleDateFormat线程不安全及解决的方法

    一. 为什么SimpleDateFormat不是线程安全的? Java源代码例如以下: /** * Date formats are not synchronized. * It is recomme ...

随机推荐

  1. 什么是validationQuery

    validationQuery是用来验证数据库连接的查询语句,这个查询语句必须是至少返回一条数据的SELECT语句.每种数据库都有各自的验证语句,下表中收集了几种常见数据库的validationQue ...

  2. python一直放弃到双数的day10

    今天接着来说那个新的大知识点,函数,函数中是可以传递一个数值的,这个数值简称为参数,对于参数,他可以是任意个数和任意类型(数据类型).参数的其中一种有位置传参,函数中的参数有几个,那么就要传入几个,传 ...

  3. WinForms 快速开发的工具类。

    下面是我本人在 WinForms 开发中积累的一些心得. 1. 在父窗体中打开子窗体 直接贴代码: private void btnCompare_Click(object sender, Event ...

  4. Linux:Day17(上) gawk基础

    GNU awk: 文本处理三工具:grep,sed,awk grep,egrep,fgrep:文本过滤工具:pattern sed:行编辑器 模式空间.保持空间 awk:报告生成器,格式化文本输出: ...

  5. destruct析构函数里操作文件出现的问题

    这几天要给后台加一个记录操作日志的功能,可是项目已经开发完了不可能再去改以前的代码了,那有什么快捷的方法呢? 项目使用的ThinkPHP3.23 ,为了方便权限控制,后台控制器结构为:普通控制器 ex ...

  6. 译注(3): NULL-计算机科学上最糟糕的失误

    原文:the worst mistake of computer science 注释:有些术语不知道怎么翻译,根据自己理解的意思翻译了,如有不妥,敬请提出:) 致谢: @vertextao @fra ...

  7. Http和Https有什么区别

    以前去面试的时候,好几家公司都会问到这个问题:http和https有什么区别? 最近突然想恶补一些基础,再夯实一下自己实力,毕竟强大的能力才是工资的保证嘛,今天就来简单记录一下htttp和https的 ...

  8. P4145 上帝造题的七分钟2 / 花神游历各国(线段树区间开平方)

    有点意思,不需要什么懒标记之类的东西,因为一个数无论怎样开平方,最后取整的结果必然会是1,所以我们不妨用最大值来维护,若区间最大值不为1,就暴力修改,否则不用管. #include<bits/s ...

  9. 一次单体测试的采坑--MatcherAssert.assertThat---org.hamcrest 和org.mockito

    单体测试测试环境ci上报这个错, 本地没问题. org.hamcrest.Matcher.describeMismatch(Ljava/lang/Object;Lorg/hamcrest/Descri ...

  10. 【很好的分享】zookeeper系列

    http://blog.csdn.net/tswisdom/article/details/41522069