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 线程非安全的问题的更多相关文章

  1. JDK中的SimpleDateFormat线程非安全

    在JDK中使用SimpleDateFormat的时候都会遇到线程安全的问题,在JDK文档中也说明了该类是线程非安全的,建议对于每个线程都创建一个SimpleDateFormat对象.如下面一个Case ...

  2. 如何在一个线程环境中使用一个线程非安全的java类

    在开发过程中 当我们拿到一个线程非安全的java类的时候,我们可以额外创建这个类的管理类 并在管理类中控制同步 比如 一个非线程安全的Pair类 package test.thread.sx.test ...

  3. 06_java 时间获取练习_Date\SimpleDateFormat\Calendar类练习

     1.获取当前的日期,并把这个日期转换为指定格式的字符串,如2088-08-08 08:08:08 import java.text.SimpleDateFormat; import java.uti ...

  4. 时间获取_Date\SimpleDateFormat\Calendar类

     1.获取当前的日期,并把这个日期转换为指定格式的字符串,如2088-08-08 08:08:08 import java.text.SimpleDateFormat; import java.uti ...

  5. 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 ...

  6. Java基础 与时间日期相关的类:System -Date -SimpleDateFormat -Calendar类 -解决后缀.000Z 的时区问题

    笔记总结: /**与时间相关的类:System_Date_SimpleDateFormat_Calendar类 * 1.system 类下的currentTimeMillis() * 输出从1970年 ...

  7. session/SessionFactory线程非安全和线程安全

    SessionFactory负责创建session,SessionFactory是线程安全的,多个并发线程可以同时访问一个 SessionFactory 并从中获取Session实例. (Sessio ...

  8. ios专题 - 多线程非GCD(1)

    iOS多线程初体验是本文要介绍的内容,iPhone中的线程应用并不是无节制的,官方给出的资料显示iPhone OS下的主线程的堆栈大小是1M,第二个线程开始都是512KB.并且该值不能通过编译器开关或 ...

  9. 【源码】HashMap源码及线程非安全分析

    最近工作不是太忙,准备再读读一些源码,想来想去,还是先从JDK的源码读起吧,毕竟很久不去读了,很多东西都生疏了.当然,还是先从炙手可热的HashMap,每次读都会有一些收获.当然,JDK8对HashM ...

随机推荐

  1. LayIM.AspNetCore Middleware 开发日记(五)Init接口实现细节

    前言 “一旦开始了就要坚持下去“.为什么本文的第一句话是这么一句话呢,因为我经常就是开头轰轰烈烈,结果越来越枯燥,就不想做下去了.但是版图就放弃又那么不甘心,继续加油吧. 吐槽完毕,进入正题.在上一篇 ...

  2. shell脚本进阶 详解及其实例(一)

    v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VM ...

  3. Spring(十一)之AOP

    Spring 框架的一个关键组件是面向方面的编程(AOP)框架.面向方面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点.跨一个应用程序的多个点的功能被称为横切关注点,这些横切关注点在概念上独立于 ...

  4. Genymotion集成到Eclipse

    在Eclipse中使用Genymotion Google的ADT中自带的模拟器速度太慢,可以使用Genymotion代替.关于Genymotion的安装方法,可以直接访问官网,需要注册账号,因为创建模 ...

  5. 2019年,200道面试题打造最受企业欢迎的iOS程序猿!

    在2018年底,小编混迹在各种iOS交流群中,整理出了将近两百道大厂最喜欢在面试问到的问题,今天在这里分享给大家[免费获取方式在最后]!                           小编就不在 ...

  6. 洛谷P4602 [CTSC2018]混合果汁(主席树)

    题目描述 小 R 热衷于做黑暗料理,尤其是混合果汁. 商店里有 nn 种果汁,编号为 0,1,\cdots,n-10,1,⋯,n−1 . ii 号果汁的美味度是 d_idi​ ,每升价格为 p_ipi ...

  7. MySQL学习【第十二篇事务中的锁与隔离级别】

    一.事务中的锁 1.啥是锁? 顾名思义,锁就是锁定的意思 2.锁的作用是什么? 在事务ACID的过程中,‘锁’和‘隔离级别’一起来实现‘I’隔离性的作用 3.锁的种类 共享锁:保证在多事务工作期间,数 ...

  8. input的默认样式去除

    outline:none;-----可去除input=text,的输入框输入时的亮边.

  9. cookie、localstorage、sessionstorage区别

      localstorage sessionsorage cookie 大小 浏览器不能保存超过300个cookie,单个服务器不能超过20个,每个cookie不能超过4k 可以达到5M 可以达到5M ...

  10. filter 图片滤镜的各种设置

    filter 图片滤镜 给当前元素加滤镜_改变它的明亮度 定义:filter 属性定义了元素(通常是<img>)的可视效果(例如:模糊与饱和度).作用在图片上或元素上.div{ },或 d ...