SimpleDateFormat线程安全问题与解决

一、简介

SimpleDateFormat类内部有一个Calendar对象引用,如下图:

SimpleDateFormat线程安全问题与解决_第1张图片

 

 

SimpleDateFormat类的parse方法中,使用了Calendar的clear方法等,如下图:

 

SimpleDateFormat线程安全问题与解决_第2张图片

 

 

SimpleDateFormat线程安全问题与解决_第3张图片

 

当我们编写DateUtil工具类时,可能会将SimpleDateFormat设置为static的,那么在并发的情况下线程共享同一个calendar,可能会出现问题。

 

 

 

二、解决办法

①去掉static,这样每次用到SimpleDateFormat的时候都会新建一个,例如四个线程执行1000个需要用到SimpleDateFormat的任务,那么将会有1000次SimpleDateFormat的创建和销毁,比较耗费资源。这里不详述。

 

②使用ThreadLocal,这样每个线程只会创建一次SimpleDateFormat。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zhenwei.liu created on 2013 13-8-29 下午5:35
 * @version $Id$
 */
public class DateUtil {

    /** 锁对象 */
    private static final Object lockObj = new Object();

    /** 存放不同的日期模板格式的sdf的Map */
    private static Map> sdfMap = new HashMap>();

    /**
     * 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
     * 
     * @param pattern
     * @return
     */
    private static SimpleDateFormat getSdf(final String pattern) {
        ThreadLocal tl = sdfMap.get(pattern);

        // 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdf
        if (tl == null) {
            synchronized (lockObj) {
                tl = sdfMap.get(pattern);
                if (tl == null) {
                    // 只有Map中还没有这个pattern的sdf才会生成新的sdf并放入map
                    System.out.println("put new sdf of pattern " + pattern + " to map");

                    // 这里是关键,使用ThreadLocal替代原来直接new SimpleDateFormat
                    tl = new ThreadLocal() {

                        @Override
                        protected SimpleDateFormat initialValue() {
                            System.out.println("thread: " + Thread.currentThread() + " init pattern: " + pattern);
                            return new SimpleDateFormat(pattern);
                        }
                    };
                    sdfMap.put(pattern, tl);
                }
            }
        }

        return tl.get();
    }

    /**
     * 是用ThreadLocal来获取SimpleDateFormat,这样每个线程只会有一个SimpleDateFormat
     * 
     * @param date
     * @param pattern
     * @return
     */
    public static String format(Date date, String pattern) {
        return getSdf(pattern).format(date);
    }

    public static Date parse(String dateStr, String pattern) throws ParseException {
        return getSdf(pattern).parse(dateStr);
    }

}

 

类似双重检验的单例模式,不难理解。

需要强调的是这里

SimpleDateFormat线程安全问题与解决_第4张图片

 这里不仅仅是简单的创建了ThreadLocal类,还重写了ThreadLocal类的InitialValue方法。

重写的作用是什么呢?当线程使用该ThreadLocal的get()方法时,若获取的为空,那么就会执行InitialValue方法初始化,这时将会初始化创建一个SimpleDateFormat类,并存放在线程类的ThreadLocalMap中。

 

ThreadLocal的get()方法源码如下:

SimpleDateFormat线程安全问题与解决_第5张图片

 

SimpleDateFormat线程安全问题与解决_第6张图片

 

我们重写了initialValue()方法,返回一个SimpleDateFormat类。

SimpleDateFormat线程安全问题与解决_第7张图片

 

 

 

也可以预先定义一些常用的日期格式,使用static块预先定义。

package com.example.test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zhenwei.liu created on 2013 13-8-29 下午5:35
 * @version $Id$
 */
public class DateUtil3 {

    /** 锁对象 */
    private static final Object lockObj = new Object();

    /** 存放不同的日期模板格式的sdf的Map */
    private static Map> sdfMap = new HashMap>();

    static{
        sdfMap.put("yyyy-MM-dd HH:mm:ss",new ThreadLocal(){
            @Override
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            }
        });
    }

    /**
     * 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
     *
     * @param pattern
     * @return
     */
    private static SimpleDateFormat getSdf(final String pattern) {
        ThreadLocal tl = sdfMap.get(pattern);

        // 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdf
        if (tl == null) {
            synchronized (lockObj) {
                tl = sdfMap.get(pattern);
                if (tl == null) {
                    // 只有Map中还没有这个pattern的sdf才会生成新的sdf并放入map
                    System.out.println("put new sdf of pattern " + pattern + " to map");

                    // 这里是关键,使用ThreadLocal替代原来直接new SimpleDateFormat
                    tl = new ThreadLocal() {

                        @Override
                        protected SimpleDateFormat initialValue() {
                            System.out.println("thread: " + Thread.currentThread() + " init pattern: " + pattern);
                            return new SimpleDateFormat(pattern);
                        }
                    };
                    sdfMap.put(pattern, tl);
                }
            }
        }

        return tl.get();
    }

    /**
     * 是用ThreadLocal来获取SimpleDateFormat,这样每个线程只会有一个SimpleDateFormat
     *
     * @param date
     * @param pattern
     * @return
     */
    public static String format(Date date, String pattern) {
        return getSdf(pattern).format(date);
    }

    public static Date parse(String dateStr, String pattern) throws ParseException {
        Date parse = getSdf(pattern).parse(dateStr);
        System.out.println(Thread.currentThread()+",parse:"+parse);
        return parse;
    }

}

 

 

学习借鉴自

https://www.cnblogs.com/zemliu/archive/2013/08/29/3290585.html

你可能感兴趣的:(软知识)