SimpleDateFormat是Java中非常常见的一个类,用来解析和格式化日期字符串。但是SimpleDateFormat在多线程的环境并不是安全的,这个是很容易犯错的部分,接下来讲一下这个问题出现的过程以及解决的思路。

问题描述:

先看代码,用来获取一个月的天数的:

importjava.text.SimpleDateFormat;importjava.util.Calendar;importjava.util.Date;public classDateUtil {/*** 获取月份天数

*@paramtime 201202

*@return

*/

public static int getDays(String time) throwsException {//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);returnday;

}

}

可以看到在这个方法里,每次要获取值的时候就先要创建一个SimpleDateFormat的实例,频繁调用这个方法的情况下很耗性能。为了避免大量实例的频繁创建和销毁,我们通常会使用单例模式或者静态变量进行改造,一般会这么改:

importjava.text.SimpleDateFormat;importjava.util.Calendar;importjava.util.Date;public classDateUtil {private static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");/*** 获取月份天数

*@paramtime 201202

*@return

*/

public static int getDays(String time) throwsException {//String time = "201202";

Date date =sdf.parse(time);

Calendar c=Calendar.getInstance();

c.setTime(date);int day =c.getActualMaximum(Calendar.DATE);returnday;

}

}

此时不管调用多少次这个方法,java虚拟机里只有一个SimpleDateFormat对象,效率和性能肯定要比第一个方法好,这个也是很多程序员选择的方法。但是,在这个多线程的条件下,多个thread共享同一个SimpleDateFormat,而SimpleDateFormat本身又是线程非安全的,这样就很容易出各种问题。

验证问题:

用一个简单的例子验证一下多线程环境下SimpleDateFormat的运行结果:

importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.concurrent.CountDownLatch;public classDateUtil {private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public staticString format(Date date) {returndateFormat.format(date);

}public static Date parse(String dateStr) throwsParseException {returndateFormat.parse(dateStr);

}public static voidmain(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(newRunnable() {

@Overridepublic voidrun() {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 2016Thread-1 Sat Feb 25 00:48:00 CST 20162017Thread-5 Sat Feb 25 00:48:00 CST 20162017Exception 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的源码:

privateStringBuffer 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 >> 8;int count = compiledPattern[i++] & 0xff;if (count == 255) {

count= compiledPattern[i++] << 16;

count|= compiledPattern[i++];

}switch(tag) {caseTAG_QUOTE_ASCII_CHAR:

toAppendTo.append((char)count);break;caseTAG_QUOTE_CHARS:

toAppendTo.append(compiledPattern, i, count);

i+=count;break;default:

subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);break;

}

}returntoAppendTo;

}

可以看到format()方法先将日期存放到一个Calendar对象中,而这个Calendar在SimpleDateFormat中是以成员变量的形式存在的。随后调用subFormat()时会再次用到成员变量Calendar。这就是问题所在。同样,在parse()方法里也会存在相应的问题。

试想,在多线程环境下,如果两个线程都使用同一个SimpleDateFormat实例,那么就有可能存在其中一个线程修改了calendar后紧接着另一个线程也修改了calendar,那么随后第一个线程用到calendar时已经不是它所期待的值了。

避免问题:

那么,如何保证SimpleDateFormat的线程安全呢?

1.每次使用SimpleDateFormat时都创建一个局部的SimpleDateFormat对象,跟一开始的那个方法一样,但是存在性能上的问题,开销较大。

2.加锁或者同步

importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Date;public classDateSyncUtil {private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static String formatDate(Date date)throwsParseException{synchronized(sdf){returnsdf.format(date);

}

}public static Date parse(String strDate) throwsParseException{synchronized(sdf){returnsdf.parse(strDate);

}

}

}

当线程较多时,当一个线程调用该方法时,其他想要调用此方法的线程就要block,多线程并发量大的时候会对性能有一定的影响。

3.使用ThreadLocal

public classDateUtil {private static ThreadLocal local = new ThreadLocal() {

@OverrideprotectedSimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

}

};public staticString format(Date date) {returnlocal.get().format(date);

}public static Date parse(String dateStr) throwsParseException {returnlocal.get().parse(dateStr);

}

}

使用ThreadLocal可以确保每个线程都可以得到一个单独的SimpleDateFormat对象,既避免了频繁创建对象,也避免了多线程的竞争。

calendar java 线程安全_SimpleDateFormat,Calendar 线程非安全的问题相关推荐

  1. calendar java 线程安全_Calendar(线程不安全)

    基本: Calendar calendar =Calendar.getInstance();int year = calendar.get(Calendar.YEAR); //得到当前时间的年份 in ...

  2. java线程runnable_java基础----多线程之Runnable(一)

    java线程的创建有两种方式,这里我们通过简单的实例来学习一下.一切都明明白白,但我们仍匆匆错过,因为你相信命运,因为我怀疑生活. java中多线程的创建 一.通过继承Thread类来创建多线程 pu ...

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

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

  4. java线程堆栈nid.tid_java排查一个线上死循环cpu暴涨的过程分析

    问题,打一个页面cpu暴涨,打开一次就涨100%,一会系统就卡的不行了. 排查方法,因为是线上的linux,没有用jvm监控工具rim链接上去. 只好用命令排查: top cpu排序,一个java进程 ...

  5. java day of month_Java Calendar实例增加DAY_OF_MONTH作为递减(仅)HOUR或MINUTE的副作用

    我有一个带有TimePicker(@ id / tyme)的主活动的an unfinished Android app,一个DatePicker(@id / date)和一个TextView(@ id ...

  6. java localdate_Java 时间类-Calendar、Date、LocalDate/LocalTime

    1.Date 类 java.util.Date是一个"万能接口",它包含日期.时间,还有毫秒数,如果你只想用java.util.Date存储日期,或者只存储时间,那么,只有你知道哪 ...

  7. 5、时间日期的处理:Java Date类、Calendar类详解

    在 Java 中获取当前时间,可以使用 java.util.Date 类和 java.util.Calendar 类完成.其中,Date 类主要封装了系统的日期和时间的信息,Calendar 类则会根 ...

  8. java clone方法_Java Calendar clone()方法与示例

    java clone方法 日历类clone()方法 (Calendar Class clone() method) clone() method is available in java.util p ...

  9. java calendar 转换_[java]转:String Date Calendar之间的转换

    1.Calendar 转化 String Calendar calendat = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDa ...

最新文章

  1. python写出表白_用Python写一个简单的表白-Go语言中文社区
  2. Android中Google Drive显示黑屏问题分析
  3. 百度,一面,二面知识点
  4. MPLS TE基本配置-IS-IS
  5. Java—正整数分解成质因数
  6. Java中的事务——全局事务与本地事务
  7. cmstop框架中的js设计content.js
  8. c++面向对象高级编程 学习十六 vptr和vtbl
  9. androidStudio项目删除模块后报错解决办法
  10. POJ--1300--Door Man【推断无向图欧拉通路】
  11. linux下proc里关于磁盘性能的参数
  12. 1.Oracle 安装教程及使用
  13. linux wine qq 字体,在UOS系统中更改deepin-wineQQ使用的字体经验谈
  14. ps抠图 淘宝抠图
  15. Win10彻底永久关闭自动更新的四种方法介绍
  16. GB28181语音对讲/摄像头公网对讲指挥
  17. Android Button英文全部大写问题
  18. python之函数len()
  19. 解决浏览器导出Excel文件名乱码问题
  20. C语言 | 求圆周长 面积 圆球表面积 体积

热门文章

  1. java 12306验证码识别_GitHub - sunqipeng-cn/JavaVerify: 用java 编写的验证码识别
  2. 【Simulink仿真与调试】新手入门第二十三天
  3. 车载触摸显示屏的工作原理
  4. java计算机毕业设计在线招投标系统源码+系统+mysql数据库+lw文档
  5. hmcl离线登陆_hmcl启动器下载
  6. python登陆成功页面跳转_模拟登陆后如何获取跳转的网页?
  7. 随机函数计算机,随机函数
  8. Pjsip信令超时机制修改
  9. Ubuntu16.04源码编译安装开源版的迅雷Xware Desktop
  10. “国际软件自由日”头脑风暴成果