实现
假设有如下代码
class Single {
public static ThreadLocal<Integer> ThreadLocalA = new ThreadLocal<>();
public static ThreadLocal<Integer> ThreadLocalB = new ThreadLocal<>();
}
class Work implements Runnable {
@Override
public void run() {
Single.ThreadLocalA.set(0);
Single.ThreadLocalB.set(0);
for(int i = 0 ;i< 10000;i ++) {
Single.ThreadLocalA.set(Single.ThreadLocalA.get() + 1);
Single.ThreadLocalB.set(Single.ThreadLocalB.get() + 1);
}
System.out.println(Single.ThreadLocalA.get());
System.out.println(Single.ThreadLocalB.get());
}
}
public class Test {
public static void main(String args[]) {
for(int cnt = 0;cnt < 10 ; cnt ++) {
Thread thread = new Thread(new Work());
thread.start();
}
}
}
首先输出结果肯定都是10000,而且会打印出二十个10000。
对于这10个线程各自而言,A变量和B变量都是各自私有的,不会干涉到其他线程的A变量和B变量。
这里面有个get方法和set方法,就从这个方法开始看。
点进get。
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
首先一上来,他调用了一个getMap方法,点进去,发现这个Map是隶属于thread的一个变量。
意思就是说,每1个thread维护着1个名为threadLocals的ThreadLocalMap数据结构。
点进Thread即可验证。
回到get方法,他拿到了每个线程独有的map之后,他会试图从map里get值,这个值的key是调用get方法的ThreadLocal对象本身。
再看set方法。
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
同样也是getmap,然后set的值的key是调用set方法的ThreadLocal对象本身。
通过上面的代码阅读,我们知道了以下信息:
1. Thread 和 ThreadLocalMap 是1 对 1 的关系
2. 一个ThreadLocalMap里会有多个MapEntry,key是ThreadLocal对象,Value是T数据类型的值。
那么按照第一段样例代码,得到的Thread,ThreadLocal,ThreadLocalMap的关系如下所示。
(其中A,B后面的数字表示是在x号线程下的变量副本)。
所以一次get流程在这个图上的体现就是(以1号线程举例)
- 首先获取自己的线程,通过线程中的threadLocals变量找到自己线程的ThreadLocalMap。
- 然后在这个Map里,通过ThreadLocal变量,找到自己线程里的值。
关于其中Weak Reference的思考
ThreadLocalMap中的Entry是Weak Reference。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
于是我去查了一下有关Weak Reference的资料
当一个对象仅仅被weak reference指向, 而没有任何其他strong reference指向的时候, 如果GC运行,那么这个对象就会被回收.
考虑下面的场景:现在有一个Product类代表一种产品,这个类被设计为不可扩展的,而此时我们想要为每个产品增加一个编号。
一种解决方案是使用HashMap(Product, Integer) 。
这种情况下,我们想要真正的回收一个Product对象,仅仅把它的强引用赋值为null是不够的,还要把相应的条目从HashMap中移除。
显然,根据前面弱引用的定义,使用弱引用能帮助我们达成这个目的。我们只需要用一个指向Product对象的弱引用对象来作为HashMap中的key就可以了。
那么把上面的这段话里所有的概念往ThreadLocalMap的场景映射一下。即我们不需要使用这个ThreadLocal对象了,要么我们自己手动操作ThreadLocalMap里的key,要么就让JVM来做这件事。
这样子ThreadLocal这个对象是不会产生内存泄漏的。
但是那个Object会有问题,因为Object是强引用,如果你不手动删除这个entry,这个Object的对象不会被回收。
所以每次使用ThreadLocal之后,建议手动使用它的remove方法。