本贴最后更新于 337 天前,其中的信息可能已经事过境迁

SimpleDateFormat 线程安全问题

SimpleDateFormat 类内部有一个 Calendar 对象引用, 它用来储存和这个 sdf 相关的日期信息, 例如 sdf.parse(dateStr), sdf.format(date) 诸如此类的方法参数传入的日期相关 String, Date 等等, 都是交 Calendar 引用来储存的
那么如果多个线程同时争抢 calendar 对象,则会出现各种问题,时间不对,线程挂死等等

怎么办呢

头铁一点,并发不高的情况下没什么问题

public class DateUtils {
    private static SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");

    public static String format(Date date){
        return dateFormat.format(date);
    }
}

浪费一点,每次都 new 一个

public class DateUtils {
    public static String format(Date date){
        SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");
        return dateFormat.format(date);
    }
}

抠门一点,大家排队共用一个

public class DateUtils {
    private static SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");

    public static synchronized String format(Date date){
        return dateFormat.format(date);
    }
}

稳健一点,每个单位分一个,单位内有序使用 很棒

public class DateUtilsDateLocal {
    private static ThreadLocal<DateFormat> threadLocal=new ThreadLocal<DateFormat>(){
        @Override
        protected  DateFormat initialValue(){
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };
    public static String format(Date date){
        return threadLocal.get().format(date);
    }
}

或者抛弃 JDK,使用其他类库中的时间格式化类:
1. 使用 Apache commons 里的 FastDateFormat,宣称是既快又线程安全的 SimpleDateFormat, 可惜它只能对日期进行 format, 不能对日期串进行解析。
2. 使用 Joda-Time 类库来处理时间相关问题

这也同时提醒我们在开发和设计系统的时候注意下一下三点:

1. 自己写公用类的时候,要对多线程调用情况下的后果在注释里进行明确说明
2. 对线程环境下,对每一个共享的可变变量都要注意其线程安全性
3. 我们的类和方法在做设计的时候,要尽量设计成无状态的

既然上面引出了 ThrealLocal 下面简单介绍一下

ThreadLocal 作用及使用方式

ThreadLocal 并不能解决同一变量的共享问题,它只是为每个线程维护了线程私有对象的拷贝,而在每个线程内,可以看作是单线程的,就不会引起并发的问题
打个比方 4 个班级 100 个学生 都要看漫画,而 ThreadLocal 就好比为 4 个班级各发一本漫画,因为班级内有老师的存在,不存在争抢的问题,每个学生都只能排队看漫画,这样就不会带来争抢的问题。

ThreadLocal 的构造函数签名是这样的:

    /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

内部啥也没做。

initialValue 函数

initialValue 函数用来设置 ThreadLocal 的初始值,函数签名如下:

    protected T initialValue() {
        return null;
    }

该函数在调用get函数的时候会第一次调用,但是如果一开始就调用了set函数,则该函数不会被调用。通常该函数只会被调用一次,除非手动调用了remove函数之后又调用get函数,这种情况下,get函数中还是会调用initialValue函数。该函数是 protected 类型的,很显然是建议在子类重载该函数的,所以通常该函数都会以匿名内部类的形式被重载,以指定初始值,比如:

public class TestThreadLocal {
    private static final ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return Integer.valueOf(1);
        }
    };
}

get 函数

该函数用来获取与当前线程关联的 ThreadLocal 的值,函数签名如下:

public T get()

如果当前线程没有该 ThreadLocal 的值,则调用initialValue函数获取初始值返回。

set 函数

set 函数用来设置当前线程的该 ThreadLocal 的值,函数签名如下:

public void set(T value)

设置当前线程的 ThreadLocal 的值为 value。

remove 函数

remove 函数用来将当前线程的 ThreadLocal 绑定的值删除,函数签名如下:

public void remove()

在某些情况下需要手动调用该函数,防止内存泄露。

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:PipeSoloSymWide 等,欢迎大家加入,贡献开源。

    3175 引用 • 3916 回帖 • 656 关注
感谢    关注    收藏    赞同    反对    举报    分享