ThreadLocal--线程本地变量,也称为线程本地存储。ThreadLocal可以让每个线程 拥有一个属于自己的变量副本,不会和其他线程的变量副本冲突,实现了线程的数据隔离。 在Android的Looper中就有对ThreadLocal的使用。
基本使用
public class UseThreadLocal {
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
t1.start();
t2.start();
}
public static class Thread1 extends Thread {
@Override
public void run() {
threadLocal.set("Thread1");
System.out.println(Thread.currentThread().getName() + "-----" + threadLocal.get());
}
}
public static class Thread2 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-----" + threadLocal.get());
}
}
}

可以看到对于同一个ThreadLocal对象而言,我们在Thread1中set了一个数据,且Thread1先于Thread2执行,但是在Thread2中却get不到数据。这就是ThreadLocal的线程隔离,每个线程都拥有自己单独的变量副本,其他线程是访问不到的。
熟悉数据结构的童鞋可能会想着用HashMap就可以自己实现一个自己的ThreadLocal。
public class MyThreadLocal<T> {
private Map<Thread, T> map = new HashMap<>();
public synchronized void set(T t){
map.put(Thread.currentThread(),t);
}
public synchronized T get(){
return map.get(Thread.currentThread());
}
}
下面把上面UseThreadLocal中的ThreadLocal换成我们自己的MyThreadLocal来看一下效果:

可以看到效果是一样的,同样做到了线程隔离。那么系统是不是这样做的呢?肯定不是!其实我们自己实现的ThreadLocal存在很大的性能问题。在线程比较多的时候会出现很激烈的竞争行为。打个比方,有十个人在操场上打一个篮球,那么这十个人就会存在对这一个篮球产生竞争的问题。那怎么解决呢?给他们每个人都发一个篮球,每个人都玩自己的篮球,那么就不会产生竞争了。但现在的情况是篮球都放在柜子里,而柜子是锁着的,那么想要拿到篮球就需要首先拿到锁的钥匙,才能打开柜子拿到篮球。虽然不争篮球了,但是现在都要去竞争柜子的锁的钥匙了。下面我们就分析一下JDK中的ThreadLocal是如何实现的。
基本原理
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
源码的注释已经写的很清楚了,就是设置指定的值给当前线程的副本。方法的逻辑也很简单,首先获取当前正在执行的线程,然后根据当前线程获得一个ThreadLocalMap对象实例。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看到ThreadLocalMap其实就是Thread类的一个成员属性

而ThreadLocalMap则是ThreadLocal的内部类。接着看map的set()方法:
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
第一行出现了一个Entry数组table。既然是数组肯定是用来存放数据的,那么到底是存放什么呢?
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
首先Entry是ThreadLocalMap的内部类,其次以ThreadLocal为键,Object为值来存储数据。熟悉HashMap源码的一定对第三行的代码不陌生,这行代码的目的是为了获取Entry在数组中的位置。拿到下标之后就从下标位置开始遍历数组。如果数组中存在Entry中的key与当前set时传入的key是同一个,则把旧值替换成新的值。存在Entry但是key为null,那么则进行替换。如果不存在上述的情况,则一直遍历直到数组中有空的位置,那么新建一个Entry对象存放到数组中。
public T get() {
Thread t = Thread.currentThread();
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();
}
get()方法的逻辑则很简单,不再进行分析。
ThreadLocal的内存泄露问题
上面我们得知Entry中只对ThreadLocal做了弱引用。弱引用的特点是如果系统中没有强引用指向它,那么下一次GC的时候是一定会被回收的。同样的我们根据上文得知Thread中持有ThreadLocalMap的引用,ThreadLocalMap则持有Entry的引用,而Entry有持有value的引用,也就是说Thread是直接持有value的。这是一种强引用,是不会被GC回收的。而线程的生命周期是很长的,比如线程池中的线程可以一直被重复利用,如果一直持有value时间长了则肯定会导致OOM。虽然在ThreadLocal的set()和get()方法中有对key为null的脏Entry进行处理,但是最好还是显式的调用remove()方法:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocal的remove方法会调用ThreadLocalMap的remove()方法:
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
// 清理脏Entry
expungeStaleEntry(i);
return;
}
}
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// value置为null 断开引用链 当下次GC的时候回收
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
网友评论