• SimpleDateFormat 的线程安全问题


    1、问题:

    SimpleDateFormat 的非线程安全问题:

    package testDate;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Locale;
    
    public class DateTester {
    
            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 < 2000; j++) {
                                    String str = testdata[i2];
                                    String str2 = null;
                                    /* synchronized(df) */{
                                        Date d = df.parse(str);
                                        str2 = df.format(d);
                                        System.out.println("i: " + i2 + "	j: " + j + "	ThreadID: "
                                                + Thread.currentThread().getId() + "	ThreadName: "
                                                + Thread.currentThread().getName() + "	" + str + "	" + 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: 1    j: 0    ThreadID: 19    ThreadName: Thread-6    14-Feb-2001 14-Jan-1999
    i: 2    j: 0    ThreadID: 20    ThreadName: Thread-7    31-Dec-2007 14-Jan-1999
    i: 0    j: 0    ThreadID: 18    ThreadName: Thread-5    01-Jan-1999 14-Jan-1999
    Exception in thread "Thread-7" Exception in thread "Thread-5" Exception in thread "Thread-6" java.lang.RuntimeException: date conversion failed after 0 iterations. Expected 31-Dec-2007 but got 14-Jan-1999java.lang.RuntimeException: date conversion failed after 0 iterations. Expected 01-Jan-1999 but got 14-Jan-1999
    
    java.lang.RuntimeException: date conversion failed after 0 iterations. Expected 14-Feb-2001 but got 14-Jan-1999
        at testDate.DateTester$1.run(DateTester.java:31)
        at testDate.DateTester$1.run(DateTester.java:31)	at testDate.DateTester$1.run(DateTester.java:31)    at java.lang.Thread.run(Thread.java:785)
    
        at java.lang.Thread.run(Thread.java:785)
    
        at java.lang.Thread.run(Thread.java:785)
    

    恩,原因你是知道了,这是由于 SimpleDateFormat 的非线程安全问题引起的,

    我们现在简化下问题,错误的代码应该是这样的:

    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");
      }
     } 
    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)最后的提问:
    上面几种方案中,有最佳方案吗?如果不是最佳,各有什么优劣?

    参考 :
    https://my.oschina.net/leejun2005/blog/152253
    https://mp.weixin.qq.com/s/AijZmBzE8Yv0XqYfZQ4URg
    http://my.oschina.net/huangyong/blog/159489
    http://my.oschina.net/huangyong/blog/159725

  • 相关阅读:
    Python3爬取前程无忧数据分析工作并存储到MySQL
    MySQL操作数据库和表的基本语句(DDL
    MyBatis的增删改查操作
    指定方向或者位置移动
    AI-Tank
    转载人家写的CURSOR
    Ajax学习整理笔记
    全面解析注解
    java调用存储过程mysql
    JAVA如何调用mysql写的存储过程
  • 原文地址:https://www.cnblogs.com/DiZhang/p/12544963.html
Copyright © 2020-2023  润新知