一、前言
最近项目中,有个同事开发中定义了一个全局变量,为了防止数据混乱【并发请求这个接口时不同线程操作,会影响变量值】,他加了一个锁,这就大大影响了性能。我默默推荐了ThreadLocal,说可以解决他的问题。最后他虽然用了ThreadLocal,但又遇到问题,说代码执行到后面发现值莫名被置空了【看了代码发现,为了不影响返回结果,异步执行了一部分业务】。。。。。
基于项目中使用到了ThreadLocal,同时一些复杂的场景,所以这篇文章将介绍ThreadLocal系列的原理,以便更好的使用。
使用ThreadLocal常见有以下的问题要思考:
(1)、主线程怎么传值给子线程
(2)、子线程修改了变量值,对主线程或其他线程是否有影响
(3)、ThreadLocal这么好用,是否无节制的使用,有什么需要注意的么?
因此,这篇文章将介绍ThreadLocal、InheritableThreadLocal(ITL)、TransmittableThreadLocal(TTL)的原理和优缺点。
二、ThreadLocal
ThreadLocal是避免多线程并发问题,避免竞争。主要是set()、get()、remove()几个方法。
2.1、为什么会用到ThreadLocal(ThreadLocal应用场景)
对时间的转化,我们经常使用SimpleDateFormat 类完成。常见使用方法:
方法一
public static String formatDate(Date date)throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
public static Date parse(String strDate) throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(strDate);
}
------将创建对象变成私有,解决多线程问题,但加重了创建对象的负担。
方法二
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date)throws ParseException{
return sdf.format(date);
}
public static Date parse(String strDate) throws ParseException{
return sdf.parse(date);
}
------new SimpleDateFormat对象虽然变成全局变量,但在多线程下会出现日期转化报错、日期转化混乱问题。
问题原因:SimpleDateFormat是线程不安全的。在parse()方法传入的类成员变量,在多线程下Calendar对象的方法会造成数据不安全的问题。
最佳考虑方法,在多线程下,是否可以为每个线程分配对应的资源,防止出现竞争错误,ThreadLocal很好的解决了多线程并发问题。
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
protected DateFormat initialValue(){
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String currentDate() {
return threadLocal.get().format(now());
}
private static ThreadLocal<DateFormat> threadLocal2 = new ThreadLocal<DateFormat>();
public static DateFormat getDateFormat(){
DateFormat df = threadLocal2.get();
if(df ==null){
df = new SimpleDateFormat(DT_FORMAT);
threadLocal2.set(df);
}
return df;
}
public static String currentDate() {
return getDateFormat().format(now());
}
2.2、ThreadLocal实现原理
目的:保证当前线程中有自己的变量,不会出现多线程并发安全问题。
实现原理:
Thread为每个线程维护唯一的ThreadLocalMap的引用,ThreadLocalMap是ThreadLocal的内部类,用Entry数组存储数据【将存储这个线程的所有ThreadLocal实例】;
Entry是继承弱引用,使用K-V方式组织数据,其中K是当前线程的不同ThreadLocal对象实例,V是存储的对象值;
主要方法是set、get、remove,都是先currentThread()获取自身线程,然后对ThreadLocalMap进行操作。不同线程拥有自身的ThreadLocalMap,因此实现“数据隔离”。
具体的对照Thread、ThreadLocal类的关系和下面的图表示
Thread{
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocal{
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
//K-V方法存储数据。K是这个Thread线程的某个ThreadLocal的实例,V是对应数值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
2.3、ThreadLocalMap
ThreadLocalMap是内部类,利用E