当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
事实上ThreadLocal是这样做的
注意这里如果取到没有该线程对应的值,会调用setInitialValue();,最终调用initialValue()生成一个值,这也是我们很多场景下要override这个方法的原因;
下面看一下getMap(Thread t)方法:
由此可见,所有的ThreadLocal的信息,最终是关联到Thread上的,线程消失后,对应的Thread对象也被回收,这时对应的ThreadLocal对象也会被回收。
最后要注意的一点是,ThreadLocalMap的Entry是一个weakReference:
这里主要因为ThreadLocalMap的key是ThreadLocal对象,如果某个ThreadLocal对象所有的强引用没有了,不能因为ThreadLocalMap的引用导致他不能被回收。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
- void set(Object value)设置当前线程的线程局部变量的值。
- public Object get()该方法返回当前线程所对应的线程局部变量。
- public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
- protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
原理
实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
demo实现
class MockThreadLocal<T> {
private Map<Thread, T> map = new HashMap<Thread, T>();
public T get() {
return (T) map.get(Thread.currentThread());
}
public void set(T value) {
map.put(Thread.currentThread(), value);
}
}
事实上ThreadLocal是这样做的
/**
* 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)
return (T)e.value;
}
return setInitialValue();
}
注意这里如果取到没有该线程对应的值,会调用setInitialValue();,最终调用initialValue()生成一个值,这也是我们很多场景下要override这个方法的原因;
下面看一下getMap(Thread t)方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
在Thread类中:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
由此可见,所有的ThreadLocal的信息,最终是关联到Thread上的,线程消失后,对应的Thread对象也被回收,这时对应的ThreadLocal对象也会被回收。
这里为什么是一个ThreadLocalMap呢,因为一个线程可以有多个ThreadLocal变量,通过map.getEntry(this)取得对应的某个具体的变量。
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
最后要注意的一点是,ThreadLocalMap的Entry是一个weakReference:
/**
* 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;
}
}
这里主要因为ThreadLocalMap的key是ThreadLocal对象,如果某个ThreadLocal对象所有的强引用没有了,不能因为ThreadLocalMap的引用导致他不能被回收。
使用例子
public class ThreadLocalTest {
// ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
public Integer initialValue() {
return 0;
}
};
// ②获取下一个序列值
public int getNextNum(int i) {
seqNum.set(seqNum.get() + i);
return seqNum.get();
}
public void set(Integer i) {
seqNum.set(i);
}
public static void main(String[] args) {
ThreadLocalTest sn = new ThreadLocalTest();
// ③ 3个线程共享sn,各自产生序列号
TestClient t1 = new TestClient(sn,1,"t1");
TestClient t2 = new TestClient(sn,2,"t2");
TestClient t3 = new TestClient(sn,3,"t3");
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread {
private ThreadLocalTest sn;
private String name;
private Integer step;
public TestClient(ThreadLocalTest sn,Integer i,String name) {
this.sn = sn;
//this.sn.set(i); 此处调用会不起作用,因为执行此段代码还是在主线程中
this.step = i;
this.name=name;
}
public void run() {
for (int i = 0; i < 3; i++) {
// ④每个线程打出3个序列值
System.out.println("thread[" + this.name + "] --> sn["
+ sn.getNextNum(this.step) + "]");
}
}
}
}
参考:
ThreadLocal浅析