SimpleDateFormat,Calendar 线程非安全的问题
SimpleDateFormat是Java中非常常见的一个类,用来解析和格式化日期字符串。但是SimpleDateFormat在多线程的环境并不是安全的,这个是很容易犯错的部分,接下来讲一下这个问题出现的过程以及解决的思路。
问题描述:
先看代码,用来获取一个月的天数的:
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date; public class DateUtil { /**
* 获取月份天数
* @param time 201202
* @return
*/
public static int getDays(String time) throws Exception {
// String time = "201202";
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
Date date = sdf.parse(time);
Calendar c = Calendar.getInstance();
c.setTime(date);
int day = c.getActualMaximum(Calendar.DATE);
return day;
} }
可以看到在这个方法里,每次要获取值的时候就先要创建一个SimpleDateFormat的实例,频繁调用这个方法的情况下很耗性能。为了避免大量实例的频繁创建和销毁,我们通常会使用单例模式或者静态变量进行改造,一般会这么改:
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date; public class DateUtil { private static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM"); /**
* 获取月份天数
* @param time 201202
* @return
*/
public static int getDays(String time) throws Exception {
// String time = "201202";
Date date = sdf.parse(time);
Calendar c = Calendar.getInstance();
c.setTime(date);
int day = c.getActualMaximum(Calendar.DATE);
return day;
} }
此时不管调用多少次这个方法,java虚拟机里只有一个SimpleDateFormat对象,效率和性能肯定要比第一个方法好,这个也是很多程序员选择的方法。但是,在这个多线程的条件下,多个thread共享同一个SimpleDateFormat,而SimpleDateFormat本身又是线程非安全的,这样就很容易出各种问题。
验证问题:
用一个简单的例子验证一下多线程环境下SimpleDateFormat的运行结果:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch; public class DateUtil {
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String format(Date date) {
return dateFormat.format(date);
} public static Date parse(String dateStr) throws ParseException {
return dateFormat.parse(dateStr);
} public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(1);
final String[] strs = new String[] {"2016-01-01 10:24:00", "2016-01-02 20:48:00", "2016-01-11 12:24:00"};
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
} for (int i = 0; i < 10; i++){
try {
System.out.println(Thread.currentThread().getName()+ "\t" + parse(strs[i % strs.length]));
Thread.sleep(100);
} catch (ParseException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
latch.countDown();
}
}
看一下运行的结果:
Thread-9 Fri Jan 01 10:24:00 CST 2016
Thread-1 Sat Feb 25 00:48:00 CST 20162017
Thread-5 Sat Feb 25 00:48:00 CST 20162017
Exception in thread "Thread-4" Exception in thread "Thread-6" java.lang.NumberFormatException: For input string: "2002.E20022E"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at DateUtil.parse(DateUtil.java:24)
at DateUtil$2.run(DateUtil.java:45)
那么为什么SimpleDateFormat不是线程安全的呢?
查找问题:
首先看一下SimpleDateFormat的源码:
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date); boolean useDateFormatSymbols = useDateFormatSymbols(); for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
} switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break; case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break; default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}
可以看到format()方法先将日期存放到一个Calendar对象中,而这个Calendar在SimpleDateFormat中是以成员变量的形式存在的。随后调用subFormat()时会再次用到成员变量Calendar。这就是问题所在。同样,在parse()方法里也会存在相应的问题。
试想,在多线程环境下,如果两个线程都使用同一个SimpleDateFormat实例,那么就有可能存在其中一个线程修改了calendar后紧接着另一个线程也修改了calendar,那么随后第一个线程用到calendar时已经不是它所期待的值了。
避免问题:
那么,如何保证SimpleDateFormat的线程安全呢?
1.每次使用SimpleDateFormat时都创建一个局部的SimpleDateFormat对象,跟一开始的那个方法一样,但是存在性能上的问题,开销较大。
2.加锁或者同步
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date; public class DateSyncUtil { private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDate(Date date)throws ParseException{
synchronized(sdf){
return sdf.format(date);
}
} public static Date parse(String strDate) throws ParseException{
synchronized(sdf){
return sdf.parse(strDate);
}
}
}
当线程较多时,当一个线程调用该方法时,其他想要调用此方法的线程就要block,多线程并发量大的时候会对性能有一定的影响。
3.使用ThreadLocal
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String format(Date date) {
return local.get().format(date);
}
public static Date parse(String dateStr) throws ParseException {
return local.get().parse(dateStr);
}
}
使用ThreadLocal可以确保每个线程都可以得到一个单独的SimpleDateFormat对象,既避免了频繁创建对象,也避免了多线程的竞争。
SimpleDateFormat,Calendar 线程非安全的问题的更多相关文章
- JDK中的SimpleDateFormat线程非安全
在JDK中使用SimpleDateFormat的时候都会遇到线程安全的问题,在JDK文档中也说明了该类是线程非安全的,建议对于每个线程都创建一个SimpleDateFormat对象.如下面一个Case ...
- 如何在一个线程环境中使用一个线程非安全的java类
在开发过程中 当我们拿到一个线程非安全的java类的时候,我们可以额外创建这个类的管理类 并在管理类中控制同步 比如 一个非线程安全的Pair类 package test.thread.sx.test ...
- 06_java 时间获取练习_Date\SimpleDateFormat\Calendar类练习
1.获取当前的日期,并把这个日期转换为指定格式的字符串,如2088-08-08 08:08:08 import java.text.SimpleDateFormat; import java.uti ...
- 时间获取_Date\SimpleDateFormat\Calendar类
1.获取当前的日期,并把这个日期转换为指定格式的字符串,如2088-08-08 08:08:08 import java.text.SimpleDateFormat; import java.uti ...
- java基础1.5版后新特性 自动装箱拆箱 Date SimpleDateFormat Calendar.getInstance()获得一个日历对象 抽象不要生成对象 get set add System.arrayCopy()用于集合等的扩容
8种基本数据类型的8种包装类 byte Byte short Short int Integer long Long float Float double Double char Character ...
- Java基础 与时间日期相关的类:System -Date -SimpleDateFormat -Calendar类 -解决后缀.000Z 的时区问题
笔记总结: /**与时间相关的类:System_Date_SimpleDateFormat_Calendar类 * 1.system 类下的currentTimeMillis() * 输出从1970年 ...
- session/SessionFactory线程非安全和线程安全
SessionFactory负责创建session,SessionFactory是线程安全的,多个并发线程可以同时访问一个 SessionFactory 并从中获取Session实例. (Sessio ...
- ios专题 - 多线程非GCD(1)
iOS多线程初体验是本文要介绍的内容,iPhone中的线程应用并不是无节制的,官方给出的资料显示iPhone OS下的主线程的堆栈大小是1M,第二个线程开始都是512KB.并且该值不能通过编译器开关或 ...
- 【源码】HashMap源码及线程非安全分析
最近工作不是太忙,准备再读读一些源码,想来想去,还是先从JDK的源码读起吧,毕竟很久不去读了,很多东西都生疏了.当然,还是先从炙手可热的HashMap,每次读都会有一些收获.当然,JDK8对HashM ...
随机推荐
- fastjson反序列化JdbcRowSetImpl
Gadget com.sun.rowset.JdbcRowSetImpl setAutoCommit() -> connect() -> InitialContext.lookup() p ...
- Odoo中的逆计算——由compute字段的值逆向修改其依赖值
转载请注明原文地址:https://www.cnblogs.com/cnodoo/p/9281406.html 当通过compute属性指定方法,根据依赖值计算得到当前字段值时.一般也要制定这个计算的 ...
- 【bbs】login.php
require的路径 整体结构的复用 渐变效果的加入 按钮:实现背景透明,文字不透明:在background-color中使用rgba,标准浏览器中,背景透明,文字不透明background-colo ...
- [Python 多线程] Semaphore、BounedeSemaphore (十二)
Semaphore 信号量,信号量对象内部维护一个倒计数器,每一次acquire都会减1,当acquire方法发现计数为0就阻塞请求的线程,直到其它线程对信号量release后,计数大于0,恢复阻塞的 ...
- JDK(七)JDK1.8源码分析【集合】TreeMap
本文转载自joemsu,原文链接 [JDK1.8]JDK1.8集合源码阅读——TreeMap(二) TreeMap是JDK中一种排序的数据结构.在这一篇里,我们将分析TreeMap的数据结构,深入理解 ...
- MyBatis保存完整日期的解决方法
在用mybatis时,对mysql数据库是datatime字段添加值是,发现添加成功后查看数据库字段值是,只有年月日有值,时分秒则为0来表示的,更改为java.sql.date,time等也不行,如果 ...
- 【luogu P1606 [USACO07FEB]荷叶塘Lilypad Pond】 题解
题目链接:https://www.luogu.org/problemnew/show/P1606 这个题..第一问很好想,但是第二问,如果要跑最短路计数的话,零边权的花怎么办? 不如这样想,如果这个点 ...
- MFC文档应用程序CToolBar:设置两个工具条并列停靠到同一条边上 转
转自:http://blog.csdn.net/panshiqu/article/details/9369891# 将多个工具条同时并列停靠在某窗口的某一条边上.对于这种停靠方法,利用上述工具条控制函 ...
- IIS中ASP.NET虚拟目录不继承主站点web.config设置的办法(转载)
ASP.NET提供了强大的Web.config来配置网站,一般来说一个网站只有一个根目录下的Web.config文件,有时候我们希望子目录有着不同的权限或者参数设置,则可以在相应子目录增加一个Web. ...
- 「微信小程序」PHP异步进程async-helper实例详解
PHP异步进程async-helper实例详解 PHP 的异步进程助手,借助于 AMQP 实现异步执行 PHP 的方法,将一些很耗时.追求高可用.需要重试机制的操作放到异步进程中去执行,将你的 HTT ...