多线程(五)ThreadLocal原理
ThreadLocal概念
ThreadLocal是一个为线程提供局部变量的工具类。为线程提供一个私有的变量副本,这样多个线程就可以随意更改自己局部的变量,而不影响别的线程。
不过,需要注意的是,ThreadLocal提供的是一个浅拷贝,如果遍历是一个引用类型,就要考虑它内部的状态是否会被改变。想要解决这个问题可以通过重写ThreadLocal的initialValue()函数来自己实现深拷贝,建议在使用ThreadLocal一开始就重写该函数。
ThreadLocal与Synchronized这样的锁是不同的,锁更多强调的是多个线程如何去正确的共享一个变量,ThreadLocal则是为了解决同一个变量如何不被多个线程共享。
ThreadLocal中有内部类ThreadLocalMap,ThreadLocalMap就是用来存放变量副本的,它的key为ThreadLocal对象还使用了弱引用。
get()方法
要获得当前线程的变量副本,首先要调用get()方法,首先会调用getMap()函数去获得当前线程的ThreadLocalMap
public T get() {
// 当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap对象
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();
}
// 获取ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
// 创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
protected T initialValue() {
return null;
}
set()和remove()方法
ThreadLocal的set()与remove()操作都是通过getMap()函数来获得ThreadLocal来进行操作。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
getMap()与 createMap()
通过这两个函数会发现ThreadLocalMap是存放在Thread中的。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal中的内存泄露
如果ThreadLocal被设置为null之后,而且没有任何强引用指向它,根据垃圾回收的可达性算法,ThreadLocal将会被回收,这样一来,ThreadLocalMap中将会含有key为null的Entry,而且ThreadLocalMap是在Thread中的,只要线程迟迟不结束,这些无法访问到的value就会形成内存泄露。为了解决这个问题,ThreadLocalMap中的getEntry(),set(),和remove()函数都会清理key 为 null 的 Entry
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);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
// 如果key为空,就清理掉
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
内存泄露
内存泄露并不是物理上的消失,而是应用程序分配某段内存之后,因为设计错误,失去了对该段内存的控制,因而造成内存的浪费。
简单来说,就是不再使用的对象的内存不能被回收,就是内存泄露。