我们知道线程也是一个「对象」,当线程这种对象想为我们提供一个「可以存取我们自定义变量的功能时」,来看下它是怎么做的。
一、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
也将被清除。 -
最后再来复习一下对象的「可到达性」
从最强到最弱,不同的可到达性级别反映了对象的生命周期。在操作上,可将它们定义如下:
- 强可到达 对象:如果某一线程可以不必遍历所有引用对象而直接到达一个对象,则该对象是强可到达 对象。新创建的对象对于创建它的线程而言是强可到达对象。
- 软可到达 对象:如果一个对象不是强可到达对象,但通过遍历某一软引用可以到达它,则该对象是软可到达 对象。
- 弱可到达 对象:如果一个对象既不是强可到达对象,也不是软可到达对象,但通过遍历弱引用可以到达它,则该对象是弱可到达 对象。当清除对某一弱可到达对象的弱引用时,便可以终止此对象了。
- 虚可到达 对象:如果一个对象既不是强可到达对象,也不是软可到达对象或弱可到达对象,它已经终止,并且某个虚引用在引用它,则该对象是虚可到达 对象。
最后,当不能以上述任何方法到达某一对象时,该对象是不可到达 对象,因此可以回收此对象。
网友评论