ThreadLocal使用场景分析,实现原理
作者:binThreadLocal和Synchonized都是为了解决线程并发的问题,但是ThreadLocal和Synchonized是完全相反的
ThreadLocal是为每个线程提供变量的副本,自己私有的变量
Synchonized是控制共享的变量只能在某个时间被一个线程用到
1.使用场景
例如给线程设置一个threadID:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class DemoTask implements Runnable {
// 包含要分配的下一个线程ID的原子整数
private static final AtomicInteger nextId = new AtomicInteger(0);
// 包含每个线程的ID的ThreadLocal变量
private static final ThreadLocal threadId = new ThreadLocal() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();// 获取值,并递增
}
};
public void run() {
System.out.printf("Starting Thread: %s : %s\n", threadId.get());
try {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Thread Finished: %s : %s\n", threadId.get());
}
}
输出的内容如下:
Starting Thread: 0 Starting Thread: 1 Starting Thread: 2 Thread Finished: 0 Thread Finished: 1 Thread Finished: 2
为了对比,我们不使用,ThreadLocal变量创建线程id试试
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class DemoTask implements Runnable {
// 包含要分配的下一个线程ID的原子整数
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final Integer threadId = nextId.getAndIncrement();
public void run() {
System.out.printf("Starting Thread: %s\n", threadId);
try {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
// startDate.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Thread Finished: %s\n", threadId);
}
}
输出的内容如下:
Starting Thread: 0 Starting Thread: 0 Thread Finished: 0 Starting Thread: 0 Thread Finished: 0 Thread Finished: 0
2.实现原理
实际上ThreadLocal的所有变量,是保存在当前线程实例中,一个类似map的属性中
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap跟当前线程绑定
Thread t = Thread.currentThread();
而这个ThreadLocalMap实际上是一个数组
Entry[] tab = table;
数组中存了Entry对象,Entry对象的key就是ThreadLocal,value是我们设置的值
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap.set中通过对ThreadLocal的hash确定槽位
int i = key.threadLocalHashCode & (len-1);
ThreadLocalMap.set(ThreadLocal> key, Object value)代码如下:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//从Thread.threadLocals中获取值
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//map.set(this, value);
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//确定hash槽位
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.get()方法
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//从Thread.threadLocals中获取值
ThreadLocalMap map = getMap(t);
if (map != null) {
//this指private static final ThreadLocal threadId = new ThreadLocal()对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
3.ThreadLocal中的弱引用
其中ThreadLocalMap中的Entry使用的是弱引用,这个很好理解,如果Entry强引用ThredLocal,那么他在线程执行完当前任务后,不会被回收,只会在线程被回时才会被回收。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
4.ThreadLocal 内存泄漏的原因分析
我们来看下图内存模型currentThread强引用了threadLocalMap,threadLocalMap中Entry的,key弱引用了threadLoacl,Entry中value强引用了threadLocal对应的值

如果程序代码执行完毕,Entry的key-threadLocal因为是弱引用,就会被GC回收,这时threadLocalMap这个key-null,变成了null,但是对应的value因为还是强引用在threadLocalMap中,所以不会被GC回收,就会造成内存泄漏
ThreadLocalMap如何解决这个问题的呢?
在get、set、remove方法中都有将key为null的情况清理掉。
其中get调用expungeStaleEntry
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;
}
set中如果发现key是null,就直接调用replaceStaleEntry方法覆盖
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) {
//值为null,将其覆盖,老的值被GC
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
remove中调用clear()方法清楚关联,同时调用expungeStaleEntry清理
/**
* Remove the entry for key.
*/
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;
}
}
}
如何避免泄露:
我们在使用ThreadLocal时,尽量在使用完后,手动去remove(),就可以有效的避免内存泄漏