Java 语言提供的线程本地存储(ThreadLocal)就能够做到避免共享
1.ThreadLocal的工作原理
一般来讲,我们的设计思路可能是ThreadLocal会持有一个Map,map的key是Thread对象,然后value存储具体数据。但是这样真是最好的设计吗?
设计的代码可能如下:
class MyThreadLocal<T> {
Map<Thread, T> locals =
new ConcurrentHashMap<>();
// 获取线程变量
T get() {
return locals.get(
Thread.currentThread());
}
// 设置线程变量
void set(T t) {
locals.put(
Thread.currentThread(), t);
}
}
但是这样可能会发生什么问题呢?
- 内存泄漏:如果Thread已经执行完毕了,但是如果ThreadLocal没有得到释放,其持有的map已经引用了已执行完成的Thread,此时就会导致Thread无法得到清理,从而导致内存泄漏
Java是如何设计的呢?
JDK中让Thread去持有上述的map,ThreadLocal其实操作的是当前线程Thread所持有的Map对象,这样就不需要使用Thread对象去作为key,能够通过当前的Thread直接获得map去给ThreadLocal类去读写,并且通过源码我们可以看到Thread持有的这个map的key的计算是根据ThreadLocal去计算的,所以get方法需要传入ThreadLocal对象去取值,代码如下:
为什么要用ThreadLocal对象去作为map的key,因为可能同时创建了多个ThreadLocal对象,如果不以其作为key,则无法区分是根据哪一个ThreadLocal对象去set的数据
class Thread {
// 内部持有 ThreadLocalMap
ThreadLocal.ThreadLocalMap
threadLocals;
}
class ThreadLocal<T>{
public T get() {
// 首先获取线程持有的
//ThreadLocalMap
ThreadLocalMap map =
Thread.currentThread()
.threadLocals;
// 在 ThreadLocalMap 中
// 查找变量
Entry e =
map.getEntry(this);
return e.value;
}
static class ThreadLocalMap{
// 内部是数组而不是 Map
Entry[] table;
// 根据 ThreadLocal 查找 Entry
Entry getEntry(ThreadLocal key){
// 省略查找逻辑
}
//Entry 定义
static class Entry extends
WeakReference<ThreadLocal>{
Object value;
}
}
}
2.ThreadLocal的内存泄漏
Java的上述实现不一定能够完全避免内存泄漏,虽然Thread持有的ThreadLocalMap中的Entry对ThreadLocal是弱引用,ThreadLocal被回收了,entry也能被回收,但是entry所存储的value是强引用,因此如果Thread得不到回收,那么ThreadLocalMap中的Entry肯定也无法回收,这样数据就一直存在,从而导致内存泄漏
解决方法:try{}finally{}方案了,finally中去清理数据(调用ThreadLocal的remove操作)
3.InheritableThreadLocal 与继承性
ThreadLocal会存在一个问题,那就是子线程无法去读取父线程中存储的数据,因为子线程没有拷贝父线程的ThreadLocalMap。因此我们只需要再子线程创建时,将父线程的ThreadLocalMap拷贝一份就行了,代码如下:
在Thread的初始化中,会将父线程inheritableThreadLocals赋值给子线程的inheritableThreadLocals,然后InheritableThreadLocal 重写了ThreadLocal的getMap的方法,这样InheritableThreadLocal.get就能拿到线程的inheritableThreadLocals(存储父线程的ThreadLocalMap数据)去读写,也就解决了子线程无法获取父线程ThreadLocal中的数据的问题
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
inheritableThreadLocals的重写get如下:
/**
* Get the map associated with a ThreadLocal.
*
* @param t the current thread
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
但是不建议使用inheritableThreadLocals,因为:线程池中线程的创建是动态的,很容易导致继承关系错乱。