0. ThreadLocal的使用:
如果开发者希望将类的某个静态变量与线程状态关联,则可以考虑使用ThreadLocal
在多线程中如果使用同一个SimpleDateFormat,并使用其parse方法时,那么会有线程安全问题
- 原因是,SimpleDateForma内部有一个Calendar对象,调用parse()方法时,先Calendar.clear(),再Calendar.add()
- 如果线程先调用了add(),然另一个线程调用了clear(),就会出现线程安全问题。
关于SimpleDateFormat安全的时间格式化线程安全问题可以参考链接
【举个栗子】:
public class ConcurrentDateUtil {
// private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static Date parse(String dateStr) throws ParseException {
SimpleDateFormat simpleDateFormat = threadLocal.get();
System.out.println(Thread.currentThread().getName() + "获取结果:" + simpleDateFormat);
Date parse = null;
try {
parse = simpleDateFormat.parse(dateStr);
System.out.println(Thread.currentThread().getName() + "解析结果:" + parse);
} catch (ParseException e) {
e.printStackTrace();
} finally {
threadLocal.remove();
}
return parse;
}
public static void main(String[] args) {
String dataStr = "2020-10-15 22:02:20";
new Thread(() -> {
try {
ConcurrentDateUtil.parse(dataStr);
} catch (ParseException e) {
e.printStackTrace();
}
}, "线程1").start();
new Thread(() -> {
try {
ConcurrentDateUtil.parse(dataStr);
} catch (ParseException e) {
e.printStackTrace();
}
}, "线程2").start();
new Thread(() -> {
try {
ConcurrentDateUtil.parse(dataStr);
} catch (ParseException e) {
e.printStackTrace();
}
}, "线程3").start();
}
【输出结果】:
结果可以看出,运行结果正常,不会出现报错。
1. ThreadLocal的原理总结:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- ConcurrentDateUtil的类对象threadLocal重写initialValue方法,为了在创建线程内部对象ThreadLocalMap时,初始化对应的value,也就是new ThreadLocalMap(threadlocal, simpleDateFormat);
- 当threadLocal.get()时,实际先获取当前的线程Thread t ,然后再获取t的内部ThreadLocalMap对象
如果获取的ThreadLocal.ThreadLocalMap为空,就新建该map
取值的时候根据当前的threadLocal对象从map获取保存的值
- 其实就是每个线程内部维护一个ThreadLocalMap map对象(并不是同一个map),来保存值,map的key是threadLocal,value为储存的值
相当于:
map1.set(threadLocal,object)
map2.set(threadLocal,object)- 虽然多线程调用的是同一个threadLocal,但线程不同,内部的map也不同,就能保证各自保存的object不被别的线程影响
【为什么上面打印的simpleDateFormat一致】:
- 明明三个线程都是new simpleDateFormat(),为什么打印是同一个?
- 因为simpleDateFormat重写了hashCode方法,对于同一个pattern,hashCode是一样的,所以toString()结果是一样的,所以打印输出结果一致
2. ThreadLocal的内存溢出:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
- ThreadLocalMap使用ThreadLocal的弱引用(WeakReference)作为Entry的key,在系统GC垃圾回收时,ThreadLocal就有可能被回收,
这样ThreadLocalMap中就会出现key为null的Entry,这样就没法访问这是为null的value,就会造成内存泄露
所以我们要在使用完threadLocal后,对其进行remove,如上述栗子。