ThreadLocal是什么
ThreadLocal是Thread Local Variable 线程局部变量,其功能就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量一样。
ThreadLocal类用法
ThreadLocal提供三个public方法
- T get(): 返回此线程局部变量中当前线程副本中的值。
- void remove(): 删除些线程局部变量中当前线程的值。
- void set(T value): 设置此线程局部变量中当前线程副本中的值。
ThreadLocal作用
- 适用于多线程情况下,在调用不同方法之间实现线程隔离的数据传递(而不用单独封装参数)。如在链路追踪中要记录traceId,spanId等。
- 适用于多线程情况下,隔离多个线程的对数据的访问冲突,避免线程对变量资源的竞争。
内存泄漏问题说明与解决
内存泄漏原因:
- 由于ThreadLocalMap中Entry数组中Entry对象的K值为弱引用,即ThreadLocal为弱引用,如果没有强引用指向它(即线程还在,但是没有用这个ThreadLocal了),它在gc中会被回收,从面key为空,但是value是强引用还在。由于key为null了,所以value无论如何访问不到了,由此存在内存泄漏问题。
- ThreadLocalMap也尽量的避免内存泄漏,其在set、get、remove处都设置了清理脏数据逻辑。(通过弱引用功能标记脏数据,然后在后面逻辑中通过标记清理脏数据)。
- 调用set(T)方法时,清理脏数据,扩容时也清理。
- 调用get()方法时,没有直接命中,后面环形查找时清理脏数据。
- 调用remove()方法时,除了清理当前Entry,还会向后清理。 解决内存泄漏问题:
- ThreadLoalMap,记得remove掉,避免数据遗留内存泄漏。
弱引用是尽量解决内存被占用的问题,通过弱引用标记,然后在set、get、remove方法中根据key为null的标记将脏数据清理,释放内存。试想,如果不用弱引用,那ThreadLocal则无法做到自动清理,导致必须手动remove。
强软弱虚引用
- 强引用(Strongly Reference):指代码中普遍存在的赋值行为,如:Object o = new Object(),只要强引用关系还在,对象就永远不会被回收。
- 软引用(Soft Reference):还有用处,但是非必须存活的对象,JVM会在内存溢出前对其进行回收,例如:缓存。
- 弱引用(Weak Reference):非必须存活的对象,引用关系比软引用还弱,不管内存是否够用,下次GC一定回收。
- 虚引用(Phantom Reference):也称“幽灵引用”、“幻影引用”,最弱的引用关系,完全不影响对象的回收,等同于没有引用,虚引用的唯一的目的是对象被回收时会收到一个系统通知。
其它
ThreadLocal类及方法简要说明
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
// 定义初始hashcode
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
// 每次new新的ThreadLocal对象时hashcode自增,用于同一线程,多个ThreadLocal对象时,设置key,value
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
// 设置线程局部变量
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,这里新建map并增加key,value值。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 移除值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
// 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();
}
// ThreadLocalMap用于保存线程局部变量。每个线程都对应一个ThreadLocalMap
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
// 设置k为弱引用,即key为弱引用
super(k);
// value为强引用
value = v;
}
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 申明entry数组用于保存Entry(key,value)对象
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
// 获取ThreadLocal对象保存值
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为空,而value不为空的对象)
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;
}
// 移除值,除了清理给定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;
}
}
}
// 设置值,并清理脏数据
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();
}
}
}