ThreadLocal线程本地存储
ThreadLocal是一种无同步的线程安全实现方式,通过在每个线程中保存一个变量副本的方式将共享数据的可见范围限制在本线程内部,从而避免并发访问造成的线程安全问题。
〇、ThreadLocal的使用
首先通过一个例子了解一下ThreadLocal的使用:
public class ThreadLocalTry {
private static ThreadLocal<Integer> intValueManager =ThreadLocal.withInitial(() -> 10);
public static void main(String[] args) {
new Thread("th1"){
@Override
public void run() {
pdate();
}
}.start();
new Thread("th2"){
@Override
public void run() {
pdate();
}
}.start();
new Thread("t23"){
@Override
public void run() {
pdate();
}
}.start();
}
private static void pdate(){
intValueManager.set(intValueManager.get()+66);
System.out.println(Thread.currentThread().getName() +":" + intValueManager.get());
}
}
示例中创建了一个Integer类型的ThreadLocal,并初始化为10,然后通过三个线程分别更新该值,而三个线程的执行结果都为76,也就是说线程之间的数据互相隔离,修改互不影响。
接下来,我们首先了解下ThreadLocal类是如何对各个线程之间的数据进行管理的,然后学习数据真正存储的容器:ThreadLocalMap。先看示意图:每个线程中包含一个_ThreadLocal.ThreadLocalMap_对象,包含以ThreadLocal对象为Key,变量值为Value的Entry数组;ThreadLocalMap中的数据由ThreadLocal管理,每个ThreadLocal管理一个数据(在各个线程中的副本)。
一、ThreadLocal
一个ThreadLocal作为key管理一个数据,可想而知主要有获取、设置值以及移除这些操作。直接show me the code:
1.1 get()
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()方法首先获取当前线程(即执行_get()_方法的线程),并获取其ThreadLocalMap对象;调用getEntry()查找该ThreadLocal管理的数据并返回,若不存在数据则初始化。
需要注意返回的result可能为空。
1.2 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);
}
set()方法同样比较简单,如果本线程存在map则调用map.set()更新值,否则利用值v创建map。看看createMap()方法,ThreadLocalMap的set()方法放在后面。
void createMap(Thread t, T firstValue) {
/**
*创建新的ThreadLocalMap对象赋给该线程的threadLocals
*/
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
1.3 remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
删除该ThreadLocal管理的变量,之后读取时仍会调用_initialValue()_方法。
二、ThreadLocalMap
public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
可以看出,在每个线程中都包含一个ThreadLocal的内部类ThreadLocalMap来保存数据,而该map的数据由ThreadLocal类来管理。
ThreadLocalMap是ThreadLocal内部实现的一个键值对序列,而非实现自Map接口;其Entry保证K为创建他的ThreadLocal,且为弱引用。
public class ThreadLocal<T> {
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold;
}
}
ThreadLocalMap内部采用了Entry数组的方式来保存数据,同样通过散列的方法确定元素的位置。看看与ThreadLocal中三个方法对应的方法。
2.1 哈希冲突
既然采用哈希存储,就不可避免会产生冲突;ThreadLocalMap中采用线性探测法解决哈希冲突,即循环向后查找下一个空的位置。
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
2.2 getEntry()
在获取线程变量时,首先在ThreadLocalMap中获取该ThreadLocal对应的Entry,然后返回其Value;在出现哈希冲突时,向后循环查找。
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1); // 由于可能出现再哈希导致散列码不确定,需要将其保存在ThreadLocal对象中
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) // 找到该ThreadLocal对应的Entry,返回
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len); // 向后查找,将e指向下一个Entry
e = tab[i];
}
return null;
}
2.3 set()
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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)]) { // 冲突(e!=null)时向后查找
ThreadLocal<?> k = e.get();
if (k == key) { // 找到对应Entry时更新value;循环中执行,条件为:e!=null && k==key
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value); // 循环结束(tab[i]==null)且未找到key对应的Entry,新建元素
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash(); // 元素数大于阈值,rehash
}
三、避免内存泄漏
由于ThreadLocalMap的key是弱引用,而Value是强引用,导致ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
避免内存泄漏,可以在使用完ThreadLocal后调用其remove()方法移除Entry节点和Map的关联,使其可以被回收。
try {
System.out.println(intValueManager.get());
}finally {
intValueManager.remove();
}