我们知道线程也是一个「对象」,当线程这种对象想为我们提供一个「可以存取我们自定义变量的功能时」,来看下它是怎么做的。
一、Thread 中的成员变量ThreadLocalMap
-
这种功能通过成员变量
java.lang.Thread#threadLocals来完成的,它是一个自定义Map类型:
public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; }可以看到它的:
-
null: 代表懒加载,只在你需要存数据时才会实例化一个ThreadLocalMap对象。 -
default: 访问修饰符,代表只想让java.lang下被访问,就是给java.lang.ThreadLocal用。
-
-
为什么不在
Thread里面给我们暴露方法?设计者觉得暴露一个类似
get()/put()方法给你,让你自己用任意的Object类型K/V操作太Low。所以他把这个功能「封装」为一个类,称为线程本地变量
java.lang.ThreadLocal,每一个你需要的变量就是一个此类的实例。
二、ThreadLocal 实现
它们的关系是,Thread has a ThreadLocalMap has a Entry has a ThreadLocal。
-
既然用
Map存,那Key和Value分别是什么?-
Key: 线程变量对象ThreadLocal。 -
Value: 你自定义操作的那个数据Object。
-
-
当然,这个内部
Map设计意图肯定要对使用者透明所以我们不能直接操作这个
Map,而是使用的ThreadLocal的三个public方法:get()/set()/remove()set 方法
public void set(T value) { // 获取当前线程 Thread t = Thread.currentThread(); // 简单的返回Thread 对象的成员变量threadLocals ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }get 方法
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { // 这个Map 没有get方法只有getEntry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) // 你之前设置的值 return (T)e.value; } return setInitialValue(); }remove 方法
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
三、ThreaLocal 使用
-
Example
JavaDoc 中已经给出了,摘抄一下:
import java.util.concurrent.atomic.AtomicInteger; public class ThreadId { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } } -
解释:初始化和
set()方法首先,之所以要使用「匿名内部类」及其对象,是因为想让你在一行代码内就完成初始化。除此之外,
initialValue()和set()的实现代码区别不大。然后,一旦哪个
Thread引用到这个ThreadLocal对象了,就会在自己的threadLocals中被设置一个值。 获取不必解释,就是从当前线程的Map中取即可。
四、WeakReference 的应用
1. 假设我们来设计那个Map和其中的Entry
-
Map 和Entry
static class ThreadLocalMap { static class Entry<K,V> { final K key; V value; } } -
我们的使用场景是这样的
// 这里注意,没有final 了 private static ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }当我们想不再使用
threadId时,我们会把threadId设置为null:public void method1(){ // ... 一些操作 threadId = null; } -
此时,GC 会清除这个对象吗?
分析之后我们发现其实大概率不会,因为那个
ThreadLocal对象并非「不可达对象」,这是因为我们的Thread.threadLocals.Entry.key仍然引用着它。只有当Thread对象本身被销毁后它才会被回收,不过在Web 项目或RPC 项目中我们一般都使用线程池,所以一般来说线程很难被销毁。很明显,我们的设计有问题。那我们看看设计者是如何做的。
2. 实际代码
-
来看看实际代码
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } // ... }Entry是个弱引用。这意味着:一旦GC 确定ThreadLocal对象是「弱可达对象」时,它将清除所有引用此对象的「弱引用」,也就是说此时Entry也将被清除。 -
最后再来复习一下对象的「可到达性」
从最强到最弱,不同的可到达性级别反映了对象的生命周期。在操作上,可将它们定义如下:
- 强可到达 对象:如果某一线程可以不必遍历所有引用对象而直接到达一个对象,则该对象是强可到达 对象。新创建的对象对于创建它的线程而言是强可到达对象。
- 软可到达 对象:如果一个对象不是强可到达对象,但通过遍历某一软引用可以到达它,则该对象是软可到达 对象。
- 弱可到达 对象:如果一个对象既不是强可到达对象,也不是软可到达对象,但通过遍历弱引用可以到达它,则该对象是弱可到达 对象。当清除对某一弱可到达对象的弱引用时,便可以终止此对象了。
- 虚可到达 对象:如果一个对象既不是强可到达对象,也不是软可到达对象或弱可到达对象,它已经终止,并且某个虚引用在引用它,则该对象是虚可到达 对象。
最后,当不能以上述任何方法到达某一对象时,该对象是不可到达 对象,因此可以回收此对象。