ThreadLocal是线程的一个本地化对象,或者说是局部变量。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了线程保持对象的方法和避免参数传递的方便的对象访问方式
ThreadLocal的应用场合,最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
概括起来说,对于多线程资源共享的问题,同步机制synchronized采用了“以时间换空间”的方式,比如定义一个static变量,同步访问,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响
下面我们就来看一下ThreadLocal的实现原理:
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("threadLocal");
threadLocal.get();
我们就从这三行代码看起
public ThreadLocal() {
}
常见TheadLocal的时候只是调用了一个默认的构造方法创建对象
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//从当前线程中获取ThreadLocalMap ,这个就是用来存储每个线程中的TheadLocal实例存储的值
ThreadLocalMap map = getMap(t);
//如果map不为空,说明已经创建过了,直接赋值
if (map != null)
map.set(this, value);
else
//否则先创建一个map,在设置上值
createMap(t, value);
}
set方法首先会获得当前线程,然后获取到当前线程所对应的一个ThreadLocalMap ,在这个map里面,以ThreadLocal实例为key,以要保存的值为value进行存储,如果这个map为null,需要先创建这个map在进行存储,接下来我们看一下这个ThreadLocalMap是怎么实现的:
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);
}
在构造方法中首先会创建一个默认大小为16的数组:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
table中要存储的Entry继承了弱引用,原来在Entry中的key对ThreadLocal存在一个弱引用,value是一个强引用,那么这不就有一个问题,如果进行了一次GC,那么就会释放key对ThreadLocal的引用,这个Entry的key就会变成null,又由于当前线程没有那么快的被释放,那么线程所持有的ThreadLocalMap就不会被释放,所以里面所有保存的Entry都不会被释放,但是key已经变成了null,永远也不会引用到这个Entry,但是仍然被引用着,所以就会导致内存泄漏。
为了避免这个情况就需要采取一些必要的措施:
① 使用结束以后进行remove操作,避免ThreadLocal对象越来越大。
② 高并发的场景:由于ThreadLocal内部使用HashMap的原理,key=currentThread,因为HashMap是非线程安全的,一定要注意hashmap.resize的时候,可能会导致某几个CPU 100%的问题,进而导致应用出现资源耗尽等不可预知的问题。
我们继续看ThreadLocalMap这个类:
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);
}
根据key获取指定的Entry,但是会有获取不到的情况,可能就是经过了一次GC,key的引用被释放了,变成了null,也可能是因为产生了hash冲突,需要继续在数组里面一个一个的找:
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)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
利用循环不但可以查找到因为冲突而被存到其他地方的Entry,还可以去除key引用被释放的Entry,如果找到了直接返回:
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 当前遍历到的下标,将这个位置释放
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 从当前下标开始遍历直到遇到元素为null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//如果key为null,说明当前Entry也需要被释放
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//否则,需要重新hash
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
在这个方法中,会清除需要被释放的Entry,同时进行rehash
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();
}
这个方法也很简单,就是如果找到了对应的key直接替换旧的值,如果对应的key为null,说明可能对应Entry需要被释放,所以就采用其他方式创建节点,否则可能就是hash冲突了,直到找到一个空的位置放上这个Entry,然后清除一些没用的Entry。
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();
expungeStaleEntry(i);
return;
}
}
}
remove方法也比较简单,删除完这个节点之后清除一些没用的Entry,下面我们再回到分析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);
}
刚才分析到这个地方了,每个线程对应一个Map,如果为null,就新创建一个然后再添加这个值,否则直接添加这个值
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();
}
首先获取当前线程,然后获取当前线程对应的Map,从Map里面找到对应的ThreadLocal,获取里面的值,如果找到了直接返回,否则返回一个初始值:
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;
}
如果没有找到对应的ThreadLocal,就会返回一个初始值,这个初始值为null,然后将这个ThreadLocal和初始值设置到Map中。