Java 线程不安全的SimpleDateFormat

SimpleDateFormat是Java提供的一个格式化和解析日期的工具类
可是因为它是线程不安全的,多线程共用一个SimpleDateFormat实例对日期进行解析或者格式化会致使程序出错java

问题重现

public class TestSimpleDateFormat {
    //(1)建立单例实例
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) {
        //(2)建立多个线程,并启动
        for (int i = 0; i <100 ; ++i) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    try {//(3)使用单例日期实例解析文本
                        System.out.println(sdf.parse("2017-12-13 15:17:27"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();//(4)启动线程
        }
    }
}安全

代码(1)建立了SimpleDateFormat的一个实例,
代码(2)建立100个线程,每一个线程都公用同一个sdf对象对文本日期进行解析,
多运行几回就会抛出java.lang.NumberFormatException异常,
加大线程的个数有利于该问题复现。多线程


问题分析

SimpleDateFormat实例里面有一个Calendar对象
SimpleDateFormat之因此是线程不安全的就是由于Calendar是线程不安全的
而Calendar之因此是线程不安全的是由于其中存放日期数据的变量都是线程不安全的,好比里面的fields,time等并发

解决方案

第一种方式:每次使用时候new一个SimpleDateFormat的实例,这样能够保证每一个实例使用本身的Calendar实例,可是每次使用都须要new一个对象,而且使用后因为没有其它引用,就会须要被回收,开销会很大。
第二种方式:能够使用synchronized进行同步,但使用同步意味着多个线程要竞争锁,在高并发场景下会致使系统响应性能降低。ide


Thread thread = new Thread(new Runnable() {
    public void run() {
        try {// (3)使用单例日期实例解析文本
            synchronized (sdf) {
                System.out.println(sdf.parse("2017-12-13 15:17:27"));
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
});高并发


第三种方式:使用ThreadLocal,这样每一个线程只须要使用一个SimpleDateFormat实例相比第一种方式大大节省了对象的建立销毁开销,而且不须要对多个线程直接进行同步,使用ThreadLocal方式代码以下:工具


public class TestSimpleDateFormat2 {
    // (1)建立threadlocal实例
    static ThreadLocal<DateFormat> safeSdf = new ThreadLocal<DateFormat>(){
        @Override 
        protected SimpleDateFormat initialValue(){
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    
    public static void main(String[] args) {
        // (2)建立多个线程,并启动
        for (int i = 0; i < 10; ++i) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    try {// (3)使用单例日期实例解析文本
                            System.out.println(safeSdf.get().parse("2017-12-13 15:17:27"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();// (4)启动线程
        }
    }
}性能


代码(1)建立了一个线程安全的SimpleDateFormat实例,步骤(3)在使用的时候首先使用get()方法获取当前线程下SimpleDateFormat的实例,在第一次调用ThreadLocal的get()方法适合会触发其initialValue方法用来建立当前线程所须要的SimpleDateFormat对象。spa

总结

SimpleDateFormat是线程不安全的,应该避免多线程下使用SimpleDateFormat的单个实例,多线程下使用时候最好使用ThreadLocal对象.net

转自:http://ifeve.com/notsafesimpledateformat/

相关文章
相关标签/搜索