SimpleDateFormat是线程不安全的
线程不安全的SimpleDateFormat
SimpleDateFormat是线程不安全的
SimpleDateFormat是Java提供的一个格式化和解析日期的工具类,日常开发中应该经常会用到,但是由于它是线程不安全的,多线程公用一个SimpleDateFormat实例对日期进行解析或者格式化会导致程序出错,本节就讨论下它为何是线程不安全的,以及如何避免。
问题复现
为了复现该问题,编写如下代码:
public class TestSimpleDateFormat {
//(1)创建单例实例
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
//(2)创建多个线程,并启动
for (int i = 0; i <10 ; ++i) {
Thread thread = new Thread(new Runnable() {
public void run() {
try {//(3)使用单例日期实例解析文本
System.out.println(sdf.parse("2017-12-13 15:17:27"));
} catch (ParseException e) {
e.printStackTrace();
}
}
});
thread.start();//(4)启动线程
}
}
}
代码(1)创建了SimpleDateFormat的一个实例,代码(2)创建10个线程,每个线程都公用同一个sdf对象对文本日期进行解析,多运行几次就会抛出java.lang.NumberFormatException异常,加大线程的个数有利于该问题复现。
问题分析
为了便于分析首先奉上SimpleDateFormat的类图结构:

可知每个SimpleDateFormat实例里面有一个Calendar对象,从后面会知道其实SimpleDateFormat之所以是线程不安全的就是因为Calendar是线程不安全的,后者之所以是线程不安全的是因为其中存放日期数据的变量都是线程不安全的,比如里面的fields,time等。
下面从代码层面看下parse方法做了什么事情:
public Date parse(String text, ParsePosition pos)
{
//(1)解析日期字符串放入CalendarBuilder的实例calb中
.....
Date parsedDate;
try {//(2)使用calb中解析好的日期数据设置calendar
parsedDate = calb.establish(calendar).getTime();
...
}
catch (IllegalArgumentException e) {
...
return null;
}
return parsedDate;
}
Calendar establish(Calendar cal) {
...
//(3)重置日期对象cal的属性值
cal.clear();
//(4) 使用calb中中属性设置cal
...
//(5)返回设置好的cal对象
return cal;
}
- 代码(1)主要的作用是解析字符串日期并把解析好的数据放入了 CalendarBuilder的实例calb中,CalendarBuilder是一个建造者模式,用来存放后面需要的数据。
- 代码(3)重置Calendar对象里面的属性值,如下代码:
public final void clear()
{
for (int i = 0; i < fields.length; ) {
stamp[i] = fields[i] = 0; // UNSET == 0
isSet[i++] = false;
}
areAllFieldsSet = areFieldsSet = false;
isTimeSet = false;
}
- 代码(4)使用calb中解析好的日期数据设置cal对象
- 代码(5) 返回设置好的cal对象
从上面步骤可知步骤(3)(4)(5)操作不是原子性操作,当多个线程调用parse
方法时候比如线程A执行了步骤(3)(4)也就是设置好了cal对象,在执行步骤(5)前线程B执行了步骤(3)清空了cal对象,由于多个线程使用的是一个cal对象,所以线程A执行步骤(5)返回的就可能是被线程B清空后的对象,当然也有可能线程B执行了步骤(4)被线程B修改后的cal对象。从而导致程序错误。
那么怎么解决那?
- 第一种方式:每次使用时候new一个SimpleDateFormat的实例,这样可以保证每个实例使用自己的Calendar实例,但是每次使用都需要new一个对象,并且使用后由于没有其它引用,就会需要被回收,开销会很大。
- 第二种方式:究其原因是因为多线程下步骤(3)(4)(5)三个步骤不是一个原子性操作,那么容易想到的是对其进行同步,让(3)(4)(5)成为原子操作,可以使用synchronized进行同步,具体如下:
public class TestSimpleDateFormat {
// (1)创建单例实例
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
// (2)创建多个线程,并启动
for (int i = 0; i < 10; ++i) {
Thread thread = new Thread(new Runnable() {
public void run() {
try {// (3)使用单例日期实例解析文本
synchronized (sdf) {
System.out.println(sdf.parse("2017-12-13 15:17:27"));
}
} catch (ParseException e) {
e.printStackTrace();
}
}
});
thread.start();// (4)启动线程
}
}
}
使用同步意味着多个线程要竞争锁,在高并发场景下会导致系统响应性能下降。
- 第三种方式:使用ThreadLocal,这样每个线程只需要使用一个SimpleDateFormat实例相比第一种方式大大节省了对象的创建销毁开销,并且不需要对多个线程直接进行同步,使用ThreadLocal方式代码如下:
public class TestSimpleDateFormat2 {
// (1)创建threadlocal实例
static ThreadLocal<DateFormat> safeSdf = new ThreadLocal<DateFormat>(){
@Override
protected SimpleDateFormat initialValue(){
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static void main(String[] args) {
// (2)创建多个线程,并启动
for (int i = 0; i < 10; ++i) {
Thread thread = new Thread(new Runnable() {
public void run() {
try {// (3)使用单例日期实例解析文本
System.out.println(safeSdf.get().parse("2017-12-13 15:17:27"));
} catch (ParseException e) {
e.printStackTrace();
}
}
});
thread.start();// (4)启动线程
}
}
}
代码(1)创建了一个线程安全的SimpleDateFormat实例,步骤(3)在使用的时候首先使用get()方法获取当前线程下SimpleDateFormat的实例,在第一次调用ThreadLocal的get()方法适合会触发其initialValue方法用来创建当前线程所需要的SimpleDateFormat对象。
第四种:建议使用Apache commons-lang中的FastDateFormat。
- <dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
- <version>2.5</version>
- </dependency>
总结
本节通过简单介绍SimpleDateFormat的原理说明了SimpleDateFormat是线程不安全的,应该避免多线程下使用SimpleDateFormat的单个实例,多线程下使用时候最好使用ThreadLocal对象。更多并发编程中需要注意的情景以及解决方法敬请期待 Java中高并发编程必备基础之并发包源码剖析 一书出版
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 线程不安全的SimpleDateFormat
SimpleDateFormat是线程不安全的的更多相关文章
- SimpleDateFormat非线程安全
文章列表 1)SimpleDateFormat的线程安全问题与解决方案 2)深入理解Java:SimpleDateFormat安全的时间格式化
- SimpleDateFormat的线程安全问题
做项目的时候查询的日期总是不对,花了很长时间才找到异常的根源,原来SimpleDateFormat是非线程安全的,当我把这个类放到多线程的环境下转换日期就会出现莫名奇妙的结果,这种异常找出来可真不容易 ...
- SimpleDateFormat 的线程安全问题与解决方式
SimpleDateFormat 的线程安全问题 SimpleDateFormat 是一个以国别敏感的方式格式化和分析数据的详细类. 它同意格式化 (date -> text).语法分析 (te ...
- 【多线程补充】SimpleDateFormat非线程安全与线程中、线程组中异常的处理
1.SimpleDateFormat非线程安全的问题 类SimpleDateFormat主要负责日期的转换与格式化,但在多线程环境中,使用此类容易造成数据转换及处理的不正确,因为SimpleDateF ...
- 044 SimpleDateFormat的线程安全问题与解决方案
这个问题,以前好像写过,不过现在这篇文章,有一个重现的过程,还是值得读一读的. URL:SimpleDateFormat的线程安全问题与解决方案
- SimpleDateFormat是线程不安全的,切忌切忌!
多线程方法中使用了共享变量SimpleDateFormat,报如下错误: java.lang.NumberFormatException: multiple points at sun.misc.F ...
- 【Java并发编程】12、ThreadLocal 解决SimpleDateFormat非线程安全
大致意思:Tim Cull碰到一个SimpleDateFormat带来的严重的性能问题,该问题主要有SimpleDateFormat引发,创建一个 SimpleDateFormat实例的开销比较昂贵, ...
- Java-JUC(十四):SimpleDateFormat是线程不安全的
SimpleDateFormat是Java提供的一个格式化和解析日期的工具类,日常开发中应该经常会用到,但是由于它是线程不安全的,多线程公用一个SimpleDateFormat实例对日期进行解析.格式 ...
- 日期格式化:SimpleDateFormat【线程不安全】、FastDateFormat和Joda-Time【后两个都是线程安全】
SimpleDateFormat是线程不安全的,不能多个线程公用.而FastDateFormat和Joda-Time都是线程安全的,可以放心使用. SimpleDateFormat是JDK提供的,不需 ...
随机推荐
- java代码-----String数组进行排序。是英文的字符串
总结:主要是方法不同了.是compareTo()方法比较字符串大小 package com.s.x; import java.util.Arrays; public class Jay { publi ...
- es6 一些小知识
本人最近被es6感兴趣了,学习一些,以下就是自己总结的一些小知识 1.当你在百度输入"es6"关键字,点击进入es6入门,首先你需要知道怎样学习的顺序,先看第21章Module语法 ...
- Windbg查看w3wp进程占用的内存及.NET内存泄露,死锁分析
https://www.cnblogs.com/startpoint/p/4194052.html https://www.cnblogs.com/lyl6796910/p/7613664.html ...
- View.findViewById()和Activity.findViewById()区别
在网上看见View.findViewById() 和 Activity.findViewById()执行效率不一样 使用Activity.findViewById()如: TextView tv_in ...
- html2pdf 中文支持问题
系统用的是HTML2PDF V4.0.3 版本 百度后 http://blog.sina.com.cn/s/blog_6b0ce0310101fdv6.html 发现中文支持不好 还是有乱码问题 解决 ...
- [Cpp primer] Namespace using Declarations
If we want to use cin in the standard library, we need to std::cin To simplify this work, we can do ...
- 5 matplotlib-绘制精美的图表
matplotlib 是python最著名的绘图库,它提供了一整套和matlab相似的命令API,十分适合交互式地进行制图.而且也可以方便地将它作为绘图控件,嵌入GUI应用程序中. 它的文档相当完备, ...
- 关于jquery.noConflict()的学习记录
今天无意中看到了jquery.noConfict()的实现方法 代码如下: var // Map over jQuery in case of overwrite _jQuery = window.j ...
- CBV 验证装饰器的使用
下面是3种方式: from django.shortcuts import render, redirect from django.views import View # Create your v ...
- IOSerialize(序列化)
在讲序列化和反序列化之前,先来阐述文件夹/文件 检查.新增.复制.移动.删除, Directory和DirectotyInfo这两个特性主要是对文件夹进行操作 首先检测文件夹是否存在 if (!Dir ...