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

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

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,367评论 19 139
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 2,963评论 3 53
  • 单例设计模式是开发中经常会被用到的一种设计模式,而且有时候一个项目中会用到多个不同的单例,但是重复的编写创建单例的...
    4fbc4e24081a阅读 646评论 0 2
  • 曾经年少的我,总是期待王子和公主的爱情。可是生活中这么多别人眼中的王子和公主,却也在爱情里一败涂地。 娱乐圈不是什...
    轲颜阅读 209评论 0 0
  • 晃晃老大的问题 最近发现晃晃老大做事不用心,敷衍了事。本想跟他动之以情晓之以理,不曾想他早已水泼不进油不粘身。我T...
    牧羊少年的时间之旅阅读 660评论 0 50

友情链接更多精彩内容