原文:https://www.jianshu.com/p/b212afa16f1f

1.SimpleDateFormat为什么不是线程安全的?

  • 如果我们把SimpleDateFormat定义成static成员变量,那么多个thread之间会共享这个SimpleDateFormat对象, 所以Calendar对象也会共享。
public static SimpleDateFormat formater = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss"); System.out.println(formater.format(new Date())+" Exception made...");
  • DateFormat.java
    public final String format(Date date)
{
return format(date, new StringBuffer(),
DontCareFieldPosition.INSTANCE).toString();
} public abstract StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition fieldPosition);
  • SimpleDateFormat.java
    @Override
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition pos)
{
// 如此轻易地使用内部变量,肯定不能线程安全
// 线程都对pos进行写操作,必然会影响其他线程的读操作
pos.beginIndex = pos.endIndex = 0;
return format(date, toAppendTo, pos.getFieldDelegate());
} 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;
}

1.1 复现错误

代码参考

public class DateFormatTest {
private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
private static String date[] = { "01-Jan-1999", "09-Jan-2000", "08-Jan-2001" , "07-Jan-2002" , "06-Jan-2003" , "05-Jan-2004" , "04-Jan-2005" , "03-Jan-2006" , "02-Jan-2007" }; public static void main(String[] args) {
for (int i = 0; i < date.length; i++) {
final int temp = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
String str1 = date[temp];
String str2 = sdf.format(sdf.parse(str1));
System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2);
if(!str1.equals(str2)){
throw new RuntimeException(Thread.currentThread().getName()
+ ", Expected " + str1 + " but got " + str2);
}
}
} catch (Exception e) {
throw new RuntimeException("parse failed", e);
}
}
}).start();
}
}
}
 
 

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

2.1 将SimpleDateFormat定义成局部变量:

SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
String str1 = "01-Jan-2010";
String str2 = sdf.format(sdf.parse(str1));

缺点:每调用一次方法就会创建一个SimpleDateFormat对象,方法结束又要作为垃圾回收。

2.2 加一把线程同步锁:synchronized(lock)

public class SyncDateFormatTest {
private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" }; public static void main(String[] args) {
for (int i = 0; i < date.length; i++) {
final int temp = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
synchronized (sdf) {
String str1 = date[temp];
Date date = sdf.parse(str1);
String str2 = sdf.format(date);
System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2);
if(!str1.equals(str2)){
throw new RuntimeException(Thread.currentThread().getName()
+ ", Expected " + str1 + " but got " + str2);
}
}
}
} catch (Exception e) {
throw new RuntimeException("parse failed", e);
}
}
}).start();
}
}
}

缺点:性能较差,每次都要等待锁释放后其他线程才能进入

2.3 使用ThreadLocal

每个线程都将拥有自己的SimpleDateFormat对象副本。

public class DateUtil {
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>(); public static Date parse(String str) throws Exception {
SimpleDateFormat sdf = local.get();
if (sdf == null) {
sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
local.set(sdf);
}
return sdf.parse(str);
} public static String format(Date date) throws Exception {
SimpleDateFormat sdf = local.get();
if (sdf == null) {
sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
local.set(sdf);
}
return sdf.format(date);
}
}
public class ThreadLocalDateFormatTest {
private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" }; public static void main(String[] args) {
for (int i = 0; i < date.length; i++) {
final int temp = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
String str1 = date[temp];
Date date = DateUtil.parse(str1);
String str2 = DateUtil.format(date);
System.out.println(str1 + "," + str2);
if(!str1.equals(str2)){
throw new RuntimeException(Thread.currentThread().getName()
+ ", Expected " + str1 + " but got " + str2);
}
}
} catch (Exception e) {
throw new RuntimeException("parse failed", e);
}
}
}).start();
}
}
}

3.使用DateTimeFormatter代替SimpleDateFormat

代码参考DateTimeFormatterTest.java
jdk1.8中新增了 LocalDate 与 LocalDateTime等类来解决日期处理方法,同时引入了一个新的类DateTimeFormatter来解决日期格式化问题。
LocalDateTime,DateTimeFormatter两个类都没有线程问题,只要你自己不把它们创建为共享变量就没有线程问题。
可以使用Instant代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
LocalDate date = LocalDate.parse("2017 06 17", formatter);
System.out.println(formatter.format(date));

自测类

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale; public class DateFormatTest {
private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy", Locale.US);
private static String date[] = { "01-01-1999", "09-01-2000", "08-01-2001" , "07-01-2002" , "06-01-2003" , "05-01-2004" , "04-01-2005" , "03-01-2006" , "02-01-2007" }; private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); public static void main(String[] args) {
for (int i = 0; i < date.length; i++) {
final int temp = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
String str1 = date[temp]; //线程安全
LocalDate date = LocalDate.parse(str1, formatter);
String str2 = formatter.format(date); //线程不安全
// String str2 = sdf.format(sdf.parse(str1)); System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2);
if(!str1.equals(str2)){
throw new RuntimeException(Thread.currentThread().getName()
+ ", Expected " + str1 + " but got " + str2);
}
}
} catch (Exception e) {
throw new RuntimeException("parse failed", e);
}
}
}).start();
}
}
}
 
 

日期时间格式化 SimpleDateFormat与DateTimeFormatter的更多相关文章

  1. Springboot 关于日期时间格式化处理方式总结

    项目中使用LocalDateTime系列作为DTO中时间的数据类型,但是SpringMVC收到参数后总报错,为了配置全局时间类型转换,尝试了如下处理方式. 注:本文基于Springboot2.x测试, ...

  2. Java魔法堂:Date与日期时间格式化

    一.前言                                                                                       日期时间的获取.显 ...

  3. PHPCMS V9调用时间标签 |日期时间格式化

    PHPCMS V9 如何调用时间标签,下面分享常见的调用时间标签 |日期时间格式化  1.日期时间格式化显示: a标准型:{date('Y-m-d H:i:s', $rs['inputtime'])} ...

  4. 【转载】Sqlserver日期时间格式化总结

    在Sqlserver数据库中,允许存储datetime的时间类型,该存储类型包含时间的时分秒以及毫秒等数值,在SQL语句查询的时候,很多时候我们需要对查询出来的日期数据进行格式化操作,Sqlserve ...

  5. Java 日期时间格式化

    在此记录Java日期时间格式化转换符,方便以后有需要时查找. 1.日期格式化 2.时间格式化 3.格式化常见的日期时间组合

  6. JavaScript日期时间格式化函数

    这篇文章主要介绍了JavaScript日期时间格式化函数分享,需要的朋友可以参考下 这个函数经常用到,分享给大家. 函数代码: //格式化参数说明: //y:年,M:月,d:日,h:时,m分,s:秒, ...

  7. js -- 日期时间格式化

    /** * js日期时间格式化 * @param date 时间读对象 * @param format 格式化字符串 例如:yyyy年MM月dd日 hh时mm分ss秒 * @returns {stri ...

  8. EasyUI datagrid 日期时间格式化

    EasyUI datagrid中显示日期时间时,会显示为以下不太直观的数值: 添加以下JavaScript脚本,然后在field中添加 formatter: DateTimeFormatter 即可. ...

  9. Java 学习 时间格式化(SimpleDateFormat)与历法类(Calendar)用法详解

    基于Android一些时间创建的基本概念 获取当前时间 方式一: Date date = new Date(); Log.e(TAG, "当前时间="+date); 结果: E/T ...

随机推荐

  1. HTML布局水平导航条2制作

    前两个博文导航条都不是铺满水平的浏览器的,很多导航条样式都是随着浏览器的移动,是100%.此外前两个博文导航条都是块状点击的,也就是给a标签加宽高,设置成块状显示,点击的时候不一定要点文字,只要点击该 ...

  2. axios 使用入门

    [Vue 牛刀小试]:第十五章 - 传统开发模式下的 axios 使用入门   一.前言# 在没有接触 React.Angular.Vue 这类 MVVM 的前端框架之前,无法抛弃 Jquery 的重 ...

  3. package ‘RPMM’ is not available (for R version 3.6.0)

    当我们用启动R安装一些R包的时候 提示: Warning: unable to access index for repository https://mirrors.eliteu.cn/CRAN/s ...

  4. Extjs GridField 总结

    此代码为完整代码,其中包含定位.使用 Enter 键,来实现 Tab 键. Ext.define('xxx.recordBook.view.EditGrid', { extend: 'Ext.form ...

  5. JVM(四) 垃圾回收

    1. 堆内存结构 Java堆从GC的角度可以细分为:新生代(Eden区.From Survivor区和To Survivor区)和老年代. 1.1 新生代 新生代是用来存放新生的对象.一般占据堆的1/ ...

  6. [EF] - "已有打开的与此 Command 相关联的 DataReader,必须首先将它关闭" 之解决

    错误 解决 在 ConnectionString 中添加 MultipleActiveResultSets=true(适用于SQL 2005以后的版本).MultipleActiveResultSet ...

  7. python 2.7 环境配置

    原文地址:Python 2.7的安装(64位win10) Python 2.7.12 下载地址:https://www.python.org/downloads/ 安装路径D:\Program Fil ...

  8. idea 默认全局配置maven,避免每次新建项目都需要指定自己的maven目录

      版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/qq_28624243/article/details/84199937 File->Oth ...

  9. go 数据渲染到html页面 02

    渲染到浏览器页面 //把数据渲染到浏览器 package main import ( "fmt" "text/template" "net/http& ...

  10. hdu 6625 three array (01-trie)

    大意: 给两个数组$a,b$, 要求重排使得$c_i=a_i\oplus b_i$字典序最小. 字典树上贪心取$n$次, 然后排序, 还不知道怎么证. #include <iostream> ...