关于 SimpleDateFormat 的非线程安全问题及其解决方案

标签: simpledateformat 线程安全 问题 | 发表时间:2016-06-08 18:15 | 作者:
出处:https://canann.iteye.com
参考:http://my.oschina.net/leejun2005/blog/152253

目录[-]
1、问题:
2、解决方案
(1)使用局部变量:
(2)使用 ThreadLocal
(3)同步代码块 synchronized(code)
(4)使用第三方的日期处理函数:
(5)最后的提问:
REF:
之前有同事好几次都掉这个坑里去了,刚好今天有看到有篇帖子提了下,索性就整理下吧~

1、问题:

先来看一段可能引起错误的代码:

package test.date;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class ProveNotSafe {
    static SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
    static String testdata[] = { "01-Jan-1999", "14-Feb-2001", "31-Dec-2007" };

    public static void main(String[] args) {
        Runnable r[] = new Runnable[testdata.length];
        for (int i = 0; i < r.length; i++) {
            final int i2 = i;
            r[i] = new Runnable() {
                public void run() {
                    try {
                        for (int j = 0; j < 1000; j++) {
                            String str = testdata[i2];
                            String str2 = null;
                            /* synchronized(df) */{
                                Date d = df.parse(str);
                                str2 = df.format(d);
                                System.out.println("i: " + i2 + "\tj: " + j + "\tThreadID: "
                                        + Thread.currentThread().getId() + "\tThreadName: "
                                        + Thread.currentThread().getName() + "\t" + str + "\t" + str2);
                            }
                            if (!str.equals(str2)) {
                                throw new RuntimeException("date conversion failed after " + j
                                        + " iterations. Expected " + str + " but got " + str2);
                            }
                        }
                    } catch (ParseException e) {
                        throw new RuntimeException("parse failed");
                    }
                }
            };
            new Thread(r[i]).start();
        }
    }
}
结果(随机失败):
i: 2 j: 0 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 1 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 2 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 3 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 4 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 5 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 6 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 7 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 8 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 9 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 10 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 11 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 12 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 13 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 14 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 15 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 16 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 31-Dec-2007
i: 2 j: 17 ThreadID: 10 ThreadName: Thread-2 31-Dec-2007 11-Jan-1999
i: 0 j: 0 ThreadID: 8 ThreadName: Thread-0 01-Jan-1999 11-Jan-1999
Exception in thread "Thread-2" i: 1 j: 0 ThreadID: 9 ThreadName: Thread-1 14-Feb-2001 11-Jan-2001
Exception in thread "Thread-0" java.lang.RuntimeException: date conversion failed after 0 iterations. Expected 01-Jan-1999 but got 11-Jan-1999
    at test.date.ProveNotSafe$1.run(ProveNotSafe.java:30)
    at java.lang.Thread.run(Thread.java:619)
Exception in thread "Thread-1" java.lang.RuntimeException: date conversion failed after 0 iterations. Expected 14-Feb-2001 but got 11-Jan-2001
    at test.date.ProveNotSafe$1.run(ProveNotSafe.java:30)
    at java.lang.Thread.run(Thread.java:619)
java.lang.RuntimeException: date conversion failed after 17 iterations. Expected 31-Dec-2007 but got 11-Jan-1999
    at test.date.ProveNotSafe$1.run(ProveNotSafe.java:30)
    at java.lang.Thread.run(Thread.java:619)
恩,原因你是知道了,这是由于  SimpleDateFormat 的非线程安全问题引起的,
我们现在简化下问题,错误的代码应该是这样的:

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {
 
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
 
public String formatDate(Date input) {
      return sdf.format(input);
}
 
}


2、解决方案

(1)使用局部变量:

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {
 
private static final String SIMPLE_FORMAT = "dd/MM/yyyy";
 
public String formatDate(Date input) {
  
  if(input == null){
   return null;
  }
  
  SimpleDateFormat sdf = new SimpleDateFormat(SIMPLE_FORMAT);//local variable
  return sdf.format(input);
}
}


恩,这是线程安全的了,不是吗?

(2)使用 ThreadLocal

这里每个线程将有它自己的 SimpleDateFormat 副本。

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {

// anonymous inner class. Each thread will have its own copy of the SimpleDateFormat
private final static ThreadLocal<simpledateformat> tl = new ThreadLocal<simpledateformat>() {
  protected SimpleDateFormat initialValue() {
   return new SimpleDateFormat("dd/MM/yyyy");
  }
};http://my.oschina.net/huangyong/blog/159725 public String formatDate(Date input) {
  if (input == null) {
   return null;
  }

  return tl.get().format(input);
}
}
PS:顺便聊聊这个 ThreadLocal:

ThreadLocal 按我的理解是一个Map容器,视作其key是当前线程,value就是我们想保证数据安全一致的某对象。从它的功能上来说,应该叫做 ThreadLocalVariable(线程局部变量)更合适些。

具体的含义与作用请参考如下两篇文摘:ThreadLocal 那点事儿

http://my.oschina.net/huangyong/blog/159489

http://my.oschina.net/huangyong/blog/159725

(3)同步代码块 synchronized(code)

或者使用装饰器设计模式包装下 SimpleDateFormat ,使之变得线程安全。

(4)使用第三方的日期处理函数:

比如 JODA 来避免这些问题,你也可以使用 commons-lang 包中的 FastDateFormat 工具类。

(5)最后的提问:

上面几种方案中,有最佳方案吗?如果不是最佳,各有什么优劣?

PS:

顺便吐槽下 java 的日期处理类真TMD是个渣。。。有坑不说,关键是难用。。。这点 shell 的 date 命令应该是用户体验做的最好的了~

REF:

http://java-success.blogspot.com/2012/07/java-coding-question-and-answer-on.html

http://www.codefutures.com/weblog/andygrove/2007/10/simpledateformat-and-thread-safety.html

关于变量的线程安全问题,请参考:

java 线程安全问题之静态变量、实例变量、局部变量

http://my.oschina.net/leejun2005/blog/130043

已有 1 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [simpledateformat 线程安全 问题] 推荐:

关于 SimpleDateFormat 的非线程安全问题及其解决方案

- - 大伟
参考:http://my.oschina.net/leejun2005/blog/152253. (2)使用 ThreadLocal. (3)同步代码块 synchronized(code). (4)使用第三方的日期处理函数:. 之前有同事好几次都掉这个坑里去了,刚好今天有看到有篇帖子提了下,索性就整理下吧~.

Spring并发访问的线程安全性问题

- - 寒江孤影
和Struts一样,Spring的Controller默认是Singleton的,这意味着每个request过来,系统都会用原有的instance去处理,这样导致了两个结果:一是我们不用每次创建Controller,二是减少了对象创建和垃圾收集的时间;由于只有一个Controller的instance,当多个线程调用它的时候,它里面的instance变量就不是线程安全的了,会发生窜数据的问题.

APP 缓存数据线程安全问题探讨

- - bang’s blog
一般一个 iOS APP 做的事就是:请求数据->保存数据->展示数据,一般用 Sqlite 作为持久存储层,保存从网络拉取的数据,下次读取可以直接从 Sqlite DB 读取. 我们先忽略从网络请求数据这一环节,假设数据已经保存在 DB 里,那我们要做的事就是,ViewController 从 DB 取数据,再传给 view 渲染:.

SimpleDateFormat和FastDateFormat的效率测试,FastDateFormatr优于SimpleDateFormat

- - 企业架构 - ITeye博客
需要引入commons-lang包,Maven配置如下:. System.out.println("执行第"+num+"次");.  从运行结果来看,FastDateFormat效率明 SimpleDateFormat . 已有 0 人发表留言,猛击->> 这里<<-参与讨论. —软件人才免语言低担保 赴美带薪读研.

[原]simpledateformat线程不安全解决方案

- - bejustice的专栏
代码功能性测试和低并发测试时一切正常,但是高并发多线程性能测试时出现. java.lang.NumberFormatException: multiplepoints错误,排查原因发现是SimpleDateFormat线程不安全引起. 2      网上解决方案. 1.      局部变量,每次new SimpleDateFormat,这样开销过大,对性能影响大;.

什么是线程安全

- - CSDN博客编程语言推荐文章
线程安全是多线程领域的问题,线程安全可以简单理解为一个方法或者一个实例可以在多线程环境中使用而不会出现问题. 在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源. 如,同一内存区(变量,数组,或对象)、系统(数据库,web services等)或文件. 实际上,这些问题只有在一或多个线程向这些资源做了写操作时才有可能发生,只要资源没有发生变化,多个线程读取相同的资源就是安全的.

Servlet是否线程安全

- - 研发管理 - ITeye博客
Servlet是线程安全吗. 要解决这个问题,首先要知道什么是线程安全:.   如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码. 如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的. 或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题.

web开发中的线程安全

- - 编程 - 编程语言 - ITeye博客
在web开发中,要关注由于并发访问所导致的对某一同一个值的修改,否则信息会造成泄漏servlet是在多线程环境下的. 即可能有多个请求发给一个servelt实例,每个请求是一个线程. struts下的action也类似,同样在多线程环境下. 译:为多线程环境编写代码. 我们的controller servlet指挥创建你的Action 类的一个实例,用此实例来服务所有的请求.

Spring单实例、多线程安全、事务解析

- - zzm
 在使用Spring时,很多人可能对Spring中为什么DAO和Service对象采用单实例方式很迷惑,这些读者是这么认为的:.     DAO对象必须包含一个数据库的连接Connection,而这个Connection不是线程安全的,所以每个DAO都要包含一个不同的Connection对象实例,这样一来DAO对象就不能是单实例的了.

设计高效的线程安全的缓存--JCIP5.6读书笔记

- - ITeye博客
[本文是我对Java Concurrency In Practice 5.6的归纳和总结.  转载请注明作者和出处,  如有谬误, 欢迎在评论中指正. 几乎每一个应用都会使用到缓存, 但是设计高效的线程安全的缓存并不简单. // 使用synchronized同步整个方法解决线程安全. Memorizer1使用HashMap缓存计算结果.