【JAVA】JDK-SimpleDataFormat 线程不安全!

【问题】

publicclassProveNotSafe {
    staticSimpleDateFormat df = newSimpleDateFormat("dd-MMM-yyyy", Locale.US);
    staticString testdata[] = { "01-Jan-1999", "14-Feb-2001", "31-Dec-2007"};

    publicstaticvoidmain(String[] args) {
        Runnable r[] = newRunnable[testdata.length];
        for(inti = 0; i < r.length; i++) {
            finalinti2 = i;
            r[i] = newRunnable() {
                publicvoidrun() {
                    try{
                        for(intj = 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)) {
                                thrownewRuntimeException("date conversion failed after "+ j
                                        + " iterations. Expected "+ str + " but got "+ str2);
                            }
                        }
                    } catch(ParseException e) {
                        thrownewRuntimeException("parse failed");
                    }
                }
            };
            newThread(r[i]).start();
        }
    }
}

  

测试结果

i: 2    j: 0    ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 1    ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 2    ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 3    ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 4    ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 5    ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 6    ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 7    ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 8    ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 9    ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 10   ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 11   ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 12   ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 13   ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 14   ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 15   ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 16   ThreadID: 10    ThreadName: Thread-2    31-Dec-200731-Dec-2007

i: 2    j: 17   ThreadID: 10    ThreadName: Thread-2    31-Dec-200711-Jan-1999

i: 0    j: 0    ThreadID: 8ThreadName: Thread-0    01-Jan-199911-Jan-1999

Exception in thread "Thread-2"i: 1j: 0    ThreadID: 9ThreadName: Thread-1    14-Feb-200111-Jan-2001

Exception in thread "Thread-0"java.lang.RuntimeException: date conversion failed after 0iterations. Expected 01-Jan-1999but 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 0iterations. Expected 14-Feb-2001but 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 17iterations. Expected 31-Dec-2007but got 11-Jan-1999

    at test.date.ProveNotSafe$1.run(ProveNotSafe.java:30)

    at java.lang.Thread.run(Thread.java:619)

 

[问题原因]

SimpleDateFormat和DateFormat类不是线程安全的。我们之所以忽视线程安全的问题,是因为从SimpleDateFormat和DateFormat类提供给我们的接口上来看,实在让人看不出它与线程安全有何相干。只是在JDK文档的最下面有如下说明:

SimpleDateFormat中的日期格式不是同步的。推荐(建议)为每个线程创建独立的格式实例。如果多个线程同时访问一个格式,则它必须保持外部同步。

  JDK原始文档如下:
  Synchronization:
  Date formats are not synchronized. 
  It is recommended to create separate format instances for each thread. 
  If multiple threads access a format concurrently, it must be synchronized externally.

  下面我们通过看JDK源码来看看为什么SimpleDateFormat和DateFormat类不是线程安全的真正原因:

  SimpleDateFormat继承了DateFormat,在DateFormat中定义了一个protected属性的 Calendar类的对象:calendar。只是因为Calendar累的概念复杂,牵扯到时区与本地化等等,Jdk的实现中使用了成员变量来传递参数,这就造成在多线程的时候会出现错误。

有这样一段代码:

父类: DateFormat

protected Calendar calendar;

子类 :   SimpleDateFormat

   // Called from Format after creating a FieldDelegate
    private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);

        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);
  break;
     }
 }
        return toAppendTo;
    }

  

calendar.setTime(date)这条语句改变了calendar,稍后,calendar还会用到(在subFormat方法里),而这就是引发问题的根源。想象一下,在一个多线程环境下,有两个线程持有了同一个SimpleDateFormat的实例,分别调用format方法:
  线程1调用format方法,改变了calendar这个字段。
  中断来了。
  线程2开始执行,它也改变了calendar。
  又中断了。
  线程1回来了,此时,calendar已然不是它所设的值,而是走上了线程2设计的道路。如果多个线程同时争抢calendar对象,则会出现各种问题,时间不对,线程挂死等等。
  分析一下format的实现,我们不难发现,用到成员变量calendar,唯一的好处,就是在调用subFormat时,少了一个参数,却带来了这许多的问题。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解。
  这个问题背后隐藏着一个更为重要的问题--无状态:无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的

[解决方法]

 1.需要的时候创建新实例:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

2.同步代码块 synchronized(code)

privatestatic SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    publicstatic String formatDate(Date date)throws ParseException{
        synchronized(sdf){
            return sdf.format(date);
        }
    }

    publicstatic Date parse(String strDate) throws ParseException{
        synchronized(sdf){
            return sdf.parse(strDate);
        }
    } 

  3.使用ThreadLocal:

也是将共享变量变为独享,线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。如果对性能要求比较高的情况下,一般推荐使用这种方法。

privatestatic ThreadLocal<SimpleDateFormate> threadLocal = new ThreadLocal<SimpleDateFormate>(); 
时间: 2024-08-09 18:28:26

【JAVA】JDK-SimpleDataFormat 线程不安全!的相关文章

java io学习 线程池

Java提供的原生线程池技术处理原理很清晰,故只要使用自己的原生线程池技术一般都能满足项目的需求.java提供了很好的线程池实现,比我们自己的实现要更加健壮以及高效,同时功能也更加强大,不建议自己编写.另外有同学可能用过spring的线程池,那么spring线程池和jdk原生线程池有啥区别吗?我们查看源码和官方api可以知道SpringFrameWork 的 ThreadPoolTaskExecutor 是辅助 JDK 的 ThreadPoolExecutor 的工具类,它将属性通过 JavaB

Java中的线程

理解线程 这段时间在看<Java并发编程实战>这本书,使自己对Java多线程的理解又加深一些,感觉自己可以总结一下了,本文就讲讲与线程有关的内容吧.我们要使用线程,首先需要理解线程,前短时间我在聊聊操作系统这篇文章中提到了一点关于线程的东西,有兴趣的同学可以读一下.有一点需要理解的就是,我们虽然常说"Java多线程",但实际上线程这东西是由操作系统提供支持的,它并不是由Java本身提供支持的,所以实际上线程的实现是平台相关的!看过Object类源码的同学应该都能注意到,Ob

Java四种线程池的学习与总结

在Java开发中,有时遇到多线程的开发时,直接使用Thread操作,对程序的性能和维护上都是一个问题,使用Java提供的线程池来操作可以很好的解决问题. 一.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable(){ @Override public void run(){ //TODO Auto-generatedmethod stub } } ).start(); 那你就out太多了,new Thread的弊端如下:

并发编程—— Java 内建线程机制【上】

不理解多线程程序设计, 就无法真正理解 JavaSE 和 JDK 源码: 因为线程特性已经与Java 语言紧密地融合在一起. 如何学习多线程程序设计呢? 那看上去似乎总是显得有些神秘.首先, 必须透彻理解并发程序设计的基本原理和机制, 否则, 只是学习使用那些关键字.类的招式,恐怕只能获得Superficial 的认识, 因为多线程程序设计的难点就在于,在任何情况下都能正确工作, easily writing programs that appear to work but will fail

【转】关于Java的Daemon线程的理解

原文地址:http://www.cnblogs.com/ChrisWang/archive/2009/11/28/1612815.html 关于Java的Daemon线程的理解 网上对Java的Daemon线程的说法很多,看的人头晕. 所以自己就来总结一下: Java语言自己可以创建两种进程“用户线程”和“守护线程” 用户线程:就是我们平时创建的普通线程. 守护线程:主要是用来服务用户线程. 那么如何来区分这两种线程呢? 其实在JDK的文档中已经说明的很清楚了: * The Java Virtu

Java并发3-多线程面试题

1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速. 2) 线程和进程有什么区别? 线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务.不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间.别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据. 3) 如何在Java中实现线程? 在语言层面有两种方式.java.lang.Thread

Java中的线程Thread解析及用途

Java中的线程 进程和线程 在并发性程序中,有两个基本的执行单元:进程和线程.在Java编程语言中,并发编程大多数情况下都是和线程相关.然而,进程也是很重要的. 一个计算机系统中通常都有很多活动的进程和线程.这一点即使是在只有一个执行核心,并且在给定时刻只能执行一个线程的系统中都是存在的.单一核心的处理时间是由整个操作系统的"时间片"特性来在众多的进程和线程中共享的. 现在,计算机系统中有多个处理器或者是有多核处理器的情况越来越普遍.这就大大增强了系统执行多个进程和线程的并发性. 进

线程池是什么?Java四种线程池的使用介绍

使用线程池的好处有很多,比如节省系统资源的开销,节省创建和销毁线程的时间等,当我们需要处理的任务较多时,就可以使用线程池,可能还有很多用户不知道Java线程池如何使用?下面小编给大家分享Java四种线程池的使用方法. 线程池介绍: 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中.如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使

java DaemonThread 守护线程详解

原文链接:https://www.cnblogs.com/ziq711/p/8228255.html 用户线程和守护线程 在Java中有两类线程:User Thread(用户线程).Daemon Thread(守护线程) 用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆: 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作:只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作.Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型

java并发学习--线程池(一)

关于java中的线程池,我一开始觉得就是为了避免频繁的创建和销毁线程吧,先创建一定量的线程,然后再进行复用.但是要具体说一下如何做到的,自己又说不出一个一二三来了,这大概就是自己的学习习惯流于表面,不经常深入的结果吧.所以这里决定系统的学习一下线程池的相关知识. 自己稍微总结了一下,学习一些新的知识或者技术的时候,大概都可以分为这么几个点: 1.为什么会有这项技术,用原来的方法有什么问题. 2.这项新技术具体是怎么解决这个问题的(这时可能就要涉及到一些具体的知识点和编码了) 3.是不是使用这项技