前言
基于引用的学习,来简单的看一下ThreadLocal的实现。ThreadLocal简单来时是线程本地变量,解决共享对象(单个线程内共享)的多线程访问问题的,其不同于synchonized的关键点在于,synchronized是利用锁机制,保证共享对象在某一时刻只能被一个线程访问,但是ThreadLocal是为每个线程提供变量副本。其劣势在于为每个线程并发访问的数据建立了一个副本,会造成内存消耗。 常见的坑:没有进行remove操作,会造成内存泄露问题。(注意并不是ThreadLocal出现的地方就得调用remove,如果ThreadLocal是静态变量等情况,其生命周期和程序一样长,就不是很必要了)
内存泄露原因分析
ThreadLocalMap
static class ThreadLocalMap {
// 继承自弱引用意味着在发生gc时,如果key值ThreadLocal被发现只有一个弱引用,其会被回收,此时如果发现创建ThreadLocal的线程一直在运行的话,那么Entry对象的value值将一直得不到回收,发生内存泄漏。
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
/**
* public WeakReference(T referent) {
* super(referent);
* }
*/
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
...
// 由set方法中createMap调用
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
...
// 由map方法中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
// 此过程中会清理key值为空的Entry对象
return getEntryAfterMiss(key, i, e);
}
}
set方法
public void set(T value) {
// t为正在执行的当先线程的句柄
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// 此过程也会清理key值为空的Entry对象
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* ThreadLocal.ThreadLocalMap threadLocals = null;
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
get方法
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();
}
简单示例
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocal<String> localName = new ThreadLocal<>();
try {
localName.set("Hello World");
String name = localName.get();
System.out.println(name);
} finally {
// 此处请注意
localName.remove();
}
}
}