threadLocal是一个线程内部的存储类,可以在线程执行中存储数据,通常情况下我们创建的变量每一个线程都可以访问,而ThreadLocal则实现了线程的隔离性,保证了存储的数据只有当前线程可以访问得到
ThreadLocal使用场景
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。最常见的ThreadLocal使用场景为用来解决数据库连接、Session管理等。
ThreadLocal的核心原理
ThreadLocal是基于ThreadLocalMap来实现的线程隔离,ThreadLocalMap是一个类似于HashMap的一个K,V键值对的数据结构,内部的实现也与HashMap基本上一模一样,只不过Entry内部类有所变化,我们先来看看ThreadLocalMap中的Entry类
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap中的Entry的Key也就是ThreadLocal是弱引用,这里简单解释一下弱引用,在java中有引用的概念,即某个对象需要为一个变量所引用才能被触达,而java中提供了四种引用,强引用,软引用,弱引用以及虚引用,平时我们正常的使用的都是强引用,哪怕内存不足抛出OOM异常也不会回收被强引用的对象,,软引用则是当JVM即将OOM的时候才会回收,弱引用是当JVM进行GC时扫描到就会被回收,虚引用则是任何时候都有可能被回收.
这里解释一下为什么key采用弱引用,首先ThreadLocalMap只有当前线程可以访问,而如果当前线程访问结束而被销毁,但是当前线程在ThreadLocalMap中的存储的value还在,并且永远也访问不到,如果当前key是强引用,而该值在系统中永远无法被访问到,那么这就引起了内存泄漏,所以java开发团队考虑到这种情况,于是将Entry中的key设为弱引用,以防止内存泄漏的问题,而在调用set,get等其他方法时 会清理key为null的方法
ThreadLocal提供了一些api来提供我们使用
- get()
- set(T val)
- remove()
通过api的名称不难看出他们的功能,set是插入数据,get是获取数据,remove是删除数据,我们先来康康set方法
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 getMap(Thread t) {
return t.threadLocals;
}
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();
}
通过这里可以看出ThreadLocalMap其实是Thread中的一个变量,每一个Thread持有一个ThreadLocalMap,而ThreadLocalMap中的每一个key则是对应的ThreadLocal,而其他的方法基本与HashMap中的逻辑差不多,而多加了一步cleanSomeSlots,就是清理呗回收了的key为null的value
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();
}
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);
}
上图的table是Object[],其实就是个map,这里的map用的是线性探测解决冲突,而hashmap是用拉链法。
调用getEntryAfterMiss线性探测,过程中每碰到无效slot,调用expungeStaleEntry进行段清理;如果找到了key,则返回结果entry,所以这里get也有去除一些无效引用的作用,上面的while语句就是个线性探测。
再看一下remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
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方法相对于getEntry和set方法比较简单,直接在table中找key,如果找到了,把弱引用断了做一次段清理。
至此源码部分就介绍完了,我们简单回顾一下ThreadLocal
首先ThreadLocal是基于静态内部类ThreadLocalMap,ThreadLocalMap是一个<K,V>形式的存储数据的结构,ThreadLocalMap中的key是ThreadLocal的弱引用变量,而ThreadLocalMap是存储在Thread中的一个变量,ThreadLocal利用当前获取当前线程的ThreadLocalMap进行get,set以及remove方法