基于ThreadLocal的日期工具类

       有时候我们会使用到JDK java.text.*下的SimpleDateFormat类来对我们的日期与字符串进行格式化得转换,此时我们很容易想到,要基于SimpleDateFormat封装成一个工具类,笔者一开始的代码类似下面:

package com.zhegui.utils.date;

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

/**
 * @author Zhegui
 *
 */
public class MyDateUtil {

    /**
     * 默认格式化模式
     */
    private static final String DEFAULT_FOMRT = "yyyy-MM-dd HH:mm:ss";

    /**
     * 全局sdf
     */
    private static SimpleDateFormat sdf = new SimpleDateFormat(); 


    /**
     * 使用默认的 pattren
     * @param date
     * @return
     */
    public static String format(Date date){
        return format(date, DEFAULT_FOMRT);
    }

    /**
     * 根据传入的pattern设置
     * @param date
     * @param pattern
     * @return
     */
    public static String format(Date date, String pattern){
        if(pattern == null || "".equals(pattern)){
            throw new IllegalArgumentException("pattern is not allow null or '' ");
        }
        return sdf.format(date);
    }

    /**
     * 使用默认的pattern
     * @param dateStr
     * @return
     */
    public static Date parse(String dateStr){
        return parse(dateStr, DEFAULT_FOMRT);
    }

    /**
     * 手动设置模式
     * @param dateStr
     * @param pattern
     * @return
     */
    public static Date parse(String dateStr, String pattern){
        Date date = null;
        try {
            date = sdf.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

       怎么样,看起来相当简单,然而后来笔者了解到,实际上上面的代码是存在问题的。什么问题呢?那就是上面的代码会存在并发问题,原因在于SimpleDateFormat不是线程安全的,而上面的写法,将SimpleDateFormat作为static,为全局共享。SimpleDateFormat内部,使用成员变量calendar存储我们的Date(DateStr)信息,当我们调用parse或者format的时候,SimpleDateFormat会使用成员变量calendar(注意是成员变量)进行一些处理转换等操作,此时在高并发情况下,交替调用parse或者format就会到致calendar内部信息不一样,从而得到错误的格式化结果。线程不安全更详细信息,读者可以参考这里

       为了解决这个问题,可以使用ThreadLocal,使得每个线程私有一个SimpleDateFormat,这样就不会出现线程安全问题了,代码如下:

package com.zhegui.utils.date;

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

/**
 * 日期工具类
 *    由于SimpleDateFormat不是线程安全的,使用ThreadLocal来封装,使得每个线程私有一个SimpleDateFormat
 *    
 *    业务场景举例:
 *       假设我们要做一个批量导入的功能,里面涉及到了dateFomrt的调用。
 *       大约10万条数据,为了高效处理,我们开启10个线程处理
 *       
 *       在这里由于SimpleDateFormat不是线程安全的,假设我们对于每条记录主动 new SimpleDateFormat(), 那么
 *       就需要创建10万个SimpleDateFormat对象,而使用ThreadLocal,我们只需要创建10个个SimpleDateFormat对象
 *       因为每个线程使用的是自己私有的SimpleDateFormat对象,因此是线程安全的
 * @author Zhegui
 *
 */
public class DateUtil {

    /**
     * 默认格式化模式
     */
    private static final String DEFAULT_FOMRT = "yyyy-MM-dd HH:mm:ss";

    /**
     * 重写initialValue 方法
     *    当我们使用
     *       ThreadLocal 的 get 方法的时候
     *       ThreadLocal 会先查看我们 是否主动使用了  set方法设置了值
     *       如果没有,或者set进去的被remove了,就使用 initialValue 返回的值
     *       如果不重写,initialValue 默认返回 null
     * 
     * 这里重写了 initialValue 我们就不需要手动 set的方式去添加 SimpleDateFormat 
     */
    private static final ThreadLocal<SimpleDateFormat> threadLocalSdf = new ThreadLocal<SimpleDateFormat>(){
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat();
        }
    };

    /**
     * 设置格式化模式
     * @param pattern
     */
    public static void setPattern(String pattern){
        threadLocalSdf.get().applyPattern(pattern);
    }

    /**
     * 使用默认的 pattren
     * @param date
     * @return
     */
    public static String format(Date date){
        return format(date, DEFAULT_FOMRT);
    }

    /**
     * 根据传入的pattern设置
     * @param date
     * @param pattern
     * @return
     */
    public static String format(Date date, String pattern){
        if(pattern == null || "".equals(pattern)){
            throw new IllegalArgumentException("pattern is not allow null or '' ");
        }
        setPattern(pattern);
        return threadLocalSdf.get().format(date);
    }

    /**
     * 使用默认的pattern
     * @param dateStr
     * @return
     */
    public static Date parse(String dateStr){
        return parse(dateStr, DEFAULT_FOMRT);
    }

    /**
     * 手动设置模式
     * @param dateStr
     * @param pattern
     * @return
     */
    public static Date parse(String dateStr, String pattern){
        setPattern(pattern);
        Date date = null;
        try {
            date = threadLocalSdf.get().parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

       那么到这里有个问题,如果仅仅是为了保证线程安全,完全可以在第一个例子中,不将SimpleDateFormat暴露为static,每次使用parse或者format的时候new一个SimpleDateFormat,这样也是线程安全的,不一定要使用ThreadLocal呢,其中又有什么不同呢?其实也是为了在高并发中提高效率,假设有这么一个场景:
       假设我们要做一个批量导入的功能,里面涉及到了DateUtil格式化的调用。大约10万条数据,为了高效处理,我们开启10个线程处理。假设我们对于每条记录的DateUtil 的使用的重新new SimpleDateFormat(), 那么就需要创建10万个甚至更多的SimpleDateFormat对象,而使用ThreadLocal,我们只需要创建10个个SimpleDateFormat对象,大大减少系统对SimpleDateFormat对象的创建和销毁的消耗。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值