ThreadLocal是一个关于创建线程局部变量的类。
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。
本质上,ThreadLocal是通过空间来换取时间,从而实现每个线程当中都会有一个变量的副本,这样每个线程就都会操作该副本,从而完全 规避了多线程的并发问题。
注意在使用ThreadLocal时最好把其说明成static以方便其他类直接使用,因为ThreadLocal是伴随着线程的存在而存在的。
public class ThreadLockDemo {
private static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
// 实现initialValue()
public Integer initialValue() {
return 0;
}
};
public int increase(){
local.set(local.get() + 1);
return local.get();
}
public static void main(String[] args) {
TestThread testThread = new TestThread(new ThreadLockDemo());
for (int i = 0; i < 4; i++) {
new Thread(testThread).start();
}
}
}
class TestThread implements Runnable{
private ThreadLockDemo demo ;
public TestThread(ThreadLockDemo demo) {
this.demo = demo;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ":" + demo.increase());
}
}
}
输出结果:
Thread-0:1
Thread-2:1
Thread-2:2
Thread-1:1
Thread-2:3
Thread-3:1
Thread-0:2
Thread-3:2
Thread-1:2
Thread-3:3
Thread-0:3
Thread-1:3
可以看到,各个线程中的ThreadLocal互不影响
在对ThreadLocal进行解析之前,我们先来看看与ThreadLocal相关的几个内部类。
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;
}
}、
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
}
在ThreadLocalMap可以看到有一个静态内部类Entry这是存放键值对的位置,这个类就是ThreadLocal的set方法set值存放的位置,存放在value,而根据其构造方法可以看到,这个类存放的建是一个ThreadLocal类型的数据
2.根据其table参数可以看到ThreadLocalMap在内部存放一个Entry数组来维护多线程环境下每个线程的ThreadLocal中存放的值。
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取线程中的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//map不为空,说明这个线程有对应的
//ThreadLocal的值,调用ThreadLocalMap 中对set方法
//进行设置;若为空,则设置一个ThreadLocalMap赋给Thread对象
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//根据线程对象获取ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//创建一个ThreadLocalMap,并且把当前的ThreadLocal对象作为值传进去
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//ThreadLocalMap 中对set方法
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
//对tab进行+1的循环遍历,如果到数组尾部,把i设置为0,继续遍历
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//获取在ThreadLocalMap中存放Entry中的ThreadLocal对象
//如果存放的对象与传入的相等,则直接把vlaue值替换掉
if (k == key) {
e.value = value;
return;
}
//key为null,说明之前存放的的ThreadLocalMap被回收了
//就清除对应的value,因为ThreadLocalMap是弱引用
//会在下一次垃圾回收时,被回收,但其value不保证被回收
//故用replaceStaleEntry实现回收,防止内存泄漏
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果Thread的ThreadLocalMap中没有传入的
//ThreadLocal对象就把新的对象及其value放入tab的数组中。
tab[i] = new Entry(key, value);
int sz = ++size;
// cleanSomeSlots 清楚陈旧的Entry(key == null)
// 如果没有清理陈旧的 Entry 并且数组中的元素大于了阈值,则进行 rehash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
set方法会把ThreadLocal与其存放的value放入Thread对象的ThreadLocalMap的Entry数组中。
由此可以看出ThreadLocal的设计思想是:在线程对象Thread中设置ThreadLocalMap对象,来存放ThreadLocal及其在线程中设置的value,并且把ThreadLocal作为键存放,因为每个线程的ThreadLocalMap对象都不一样,所有不会引起冲突。而ThreadLocalMap中的Entry采用弱引用保证了会被及时回收。因为每个线程都给当前ThreadLocal的副本从而确保了,每个线程的ThreadLocal中的值相互不影响。
public T get() {
//同set方法开始一样先获取当前Thread对象中的ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//如果map有值,就获取map中当前ThreadLocal作为键的Entry对象
//并且判断成功后,把Entry对象中的value直接返回出去
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果Thread的map不存在就初始化一个
return setInitialValue();
}
private T setInitialValue() {
//初始化是给map写入一个value这个initialValue可以有程序员自己重写
//其他操作和set方法类似
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
get方法会先尝试获取当前ThreadLocal对象的value,如果当前的ThreadLocal没设置value,那么就会调用其setInitialValue方法调用initialValue()方法设置一个初始值,并且把这个值返回,initialValue()可以由开发者自己进行重写。
从3对ThreadLocal的源码分析可以得出这样一个图
这里采用了弱引用的方式解决了ThreadLocal的内存泄漏(即存在于Thread的ThreadLocal不会吧回收从而造成内存溢出)。那些在栈中ThreadLocal的引用如果被删除,那么对象就只有一个弱引用会在下一次垃圾回收时被回收。
但是仅仅依赖弱引用仍然不能完全解决内存泄漏问题,因为Entry是一个键值对,键被回收,但值依旧存在,仍然会造成内存泄漏。所以ThreadLocal类的设计人员在代码中也加入了key为null时的回收处理,。如在set的源码分析中就有体现,在get方法中也有。当然如果想更好的进行回收,可以显示调用ThreadLocal的remove()方法进行处理。