关于 SimpleDateFormat 的线程安全问题

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

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()

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

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3165 引用 • 8206 回帖 • 1 关注
  • 线程
    120 引用 • 111 回帖 • 3 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...