初看ThreadLocal代码时候,感觉到很绕。区区三个类,但他们之间的类关系和设计思路与我们平常写的业务代码是不太一样的。
让我们一起来看看这三个类之间错综复杂的关系吧。
从上图我们可以发现Thread 中持有一个ThreadLocalMap ,这里你可以简单理解为就是持有一个数组,这个数组是Entry 类型的。 Entry 的key 是ThreadLocal 类型的,value 是Object 类型。也就是一个ThreadLocalMap 可以持有多个ThreadLocal。
为什么要将ThreadLocalMpa设计为ThreadLocal的内部类,而不独立出来呢?其实这里涉及到内部类起到封装的作用,我们一起看一下源码
//*/* /* ThreadLocalMap is a customized hash map suitable only for /* maintaining thread local values. No operations are exported /* outside of the ThreadLocal class. The class is package private to /* allow declaration of fields in class Thread. To help deal with /* very large and long-lived usages, the hash table entries use /* WeakReferences for keys. However, since reference queues are not /* used, stale entries are guaranteed to be removed only when /* the table starts running out of space. /*/ static class ThreadLocalMap {}
主要是说明ThreadLocalMap 是一个线程本地的值,它所有的方法都是private 的,也就意味着除了ThreadLocal 这个类,其他类是不能操作ThreadLocalMap 中的任何方法的,这样就可以对其他类是透明的。同时这个类的权限是包级别的,也就意味着只有同一个包下面的类才能引用ThreadLocalMap 这个类,这也是Thread 为什么可以引用ThreadLocalMap 的原因,因为他们在同一个包下面。
虽然Thread 可以引用ThreadLocalMap,但是不能调用任何ThreadLocalMap 中的方法。这也就是我们平时都是通过ThreadLocal 来获取值和设置值,看下以下代码
public class Test {
public static void main(String/[/] args) { ThreadLocal<String> local = new ThreadLocal<>(); local.set("hello world"); System.out.println(local.get()); }
}
在第一次调用ThreadLocal set() 方法的时候开始绑定的,来我们看下set 方法的源码
我们这里看似是调用ThreadLocal的set方法设置了hello world,实际上这个值已经被设置到当前线程Thread拥有的ThreadLocalMap中去了。
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else //第一次的时候进来这里,因为ThreadLocalMap 还没和Thread 绑定 createMap(t, value); } //这个时候开始创建一个新的ThreadLocalMap 赋值给Thread 进行绑定 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
我们调用ThreadLocal 的get 方法的时候,其实我们最后是通过调用ThreadLocalMap 来获取值的。看完了get和set方法后,我们会发现ThreadLocalMap其实是一个中间媒介,对于使用者而言我们与我们直接打交道的是ThreadLocal,只不过我们要想把值和线程绑定在一起是离不开ThreadLocalMap这个媒介的。
public T get() { //这里通过获取当前的线程 Thread t = Thread.currentThread(); //通过线程来获取ThreadLocalMap ,还记得我们上面说的Thread 里面有一个ThreadLocalMap 属性吗?就是这里用上了 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(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
现在大家应该明白了,其实ThreadLdocalMap 对使用者来说是透明的,可以当作空气,我们一值使用的都是ThreadLocal,这样的设计在使用的时候就显得简单,然后封装性又特别好。