美文网首页
SimpleDateFormat 线程安全的问题以及解决方案

SimpleDateFormat 线程安全的问题以及解决方案

作者: ggr | 来源:发表于2018-06-30 13:39 被阅读0次

    之前在一本书上看到一个比较有意思的问题如下:

    public class MyThread extends Thread {
    
        private SimpleDateFormat dateFormat;
    
        private String dateString;
    
        MyThread(){
            super();
        }
        MyThread(SimpleDateFormat dateFormat,String dateString){
            super();
            this.dateFormat =dateFormat;
            this.dateString = dateString;
        }
    
        @Override
        public void run() {
            try{
                java.util.Date dateStr = dateFormat.parse(dateString);
                String newDateString = dateFormat.format(dateStr);
                if(!newDateString.equals(dateString)){
                    System.out.println("时间转换出错了dateString="+dateString+"newDateString="+newDateString);
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
    
    public class FormatErrorTest {
        public static void main(String[] args) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String[] dateStrings = new String[]{"2018-08-01","2018-08-02","2018-08-03","2018-08-04","2018-08-05","2018-08-06","2018-08-07","2018-08-08"};
            MyThread[] myThreads = new MyThread[dateStrings.length];
    
            for(int i=0;i<dateStrings.length;i++){
                myThreads[i] = new MyThread(dateFormat,dateStrings[i]);
            }
    
            for(int i=0;i<dateStrings.length;i++){
                myThreads[i].start();
            }
        }
    }
    
    • 思考这个main方法执行会不会出现问题?
      运行结果如下:


      image.png

    其实SimpleDateFormat 是线程不安全。我自己看了一下源代码
    其实SimpleDateFormat 这个类我们主要用的还是他的parse方法和format方法。
    部分源码如下:

    public Date parse(String text, ParsePosition pos){
             //...
     }
    
    //StringBuffer 虽然是线程安全的,但是不能保证在参数传入阶段的线程同步问题,只能保证其每次只能同时被一个线程使用。
    public StringBuffer format(Date date, StringBuffer toAppendTo,
                                   FieldPosition pos){
            //...
     }
    

    这2个方法明显没有做线程安全的控制,所以有线程安全问题也是可以理解的。如果线程A正在使用这个实例那个这个时候线程B又传入了新的时间字符串将会导致线程A得到的结果实际上可能是线程B需要的结果。

    对于SimpleDateFormat这个类,建议在使用的时候一个线程一个实例,不要当做共享资源来使用,除非你显示了对这个类的实例的获取进行了特殊的线程安全处理。目前有2种比较通用的方法来解决这个问题。如下:
    方法一,一个线程一个SimpleDateFormat实例

    public class FormatErrorTest {
        public static void main(String[] args) {
           
            String[] dateStrings = new String[]{"2018-08-01","2018-08-02","2018-08-03","2018-08-04","2018-08-05","2018-08-06","2018-08-07","2018-08-08"};
            MyThread[] myThreads = new MyThread[dateStrings.length];
            for(int i=0;i<dateStrings.length;i++){
               SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                myThreads[i] = new MyThread(dateFormat,dateStrings[i]);
            }
            for(int i=0;i<dateStrings.length;i++){
                myThreads[i].start();
            }
        }
    

    运行结果如下:


    image.png

    方法二:使用ThreadLocal处理,本质上原理都是差不多,就是在使用的时候如果ThreadLocal上面没有就新建一个放进去,如果有就直接获取。

    public class FormatErrorTest {
    
        static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>();
    
        static SimpleDateFormat getSimpleDateFormat(){
            SimpleDateFormat dateFormat = null;
            dateFormat = local.get();
            if(dateFormat==null){
                System.out.println(Thread.currentThread().getName()+"没有抢到SimpleDateFormat");
                dateFormat =new SimpleDateFormat("yyyy-MM-dd");
                local.set(dateFormat);
            }
            return dateFormat;
        }
        public static void main(String[] args) {
    
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String[] dateStrings = new String[]{"2018-08-01","2018-08-02","2018-08-03","2018-08-04","2018-08-05","2018-08-06","2018-08-07","2018-08-08"};
            MyThread[] myThreads = new MyThread[dateStrings.length];
    
            for(int i=0;i<dateStrings.length;i++){
                myThreads[i] = new MyThread(dateFormat,dateStrings[i]);
            }
    
            for(int i=0;i<dateStrings.length;i++){
                myThreads[i].start();
            }
        }
    }
    

    Mythread改成:

    import java.text.Format;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    
    /**
     *
     *  @Author: GuiRunning 郭贵荣
     *
     *  @Description:
     *
     *  @Date: 2018/6/30 11:08
     *
     */
    public class MyThread extends Thread {
    
        private String dateString;
    
        MyThread(){
            super();
        }
        MyThread(SimpleDateFormat dateFormat,String dateString){
            super();
            this.dateString = dateString;
        }
    
        @Override
        public void run() {
            try{
                java.util.Date dateStr = FormatErrorTest.getSimpleDateFormat().parse(dateString);
                String newDateString = FormatErrorTest.getSimpleDateFormat().format(dateStr);
                if(!newDateString.equals(dateString)){
                    System.out.println("时间转换出错了dateString="+dateString+"newDateString="+newDateString);
                }
                System.out.println(Thread.currentThread().getName()+"完成转换dateString="+dateString+"newDateString="+newDateString);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
    

    运行结果:


    image.png

    相关文章

      网友评论

          本文标题:SimpleDateFormat 线程安全的问题以及解决方案

          本文链接:https://www.haomeiwen.com/subject/jgwvfftx.html