ThreadLocal分析
ThreadLocal的特点是组合在了Thread内部,使得每个线程都有自己独有的数据,从而避免了线程安全问题
ThreadLocal主要体现在精妙的设计思想中。缺点不明显,使用弱引用作为手段缓解内存泄漏的发生是其中一个思想
文章目录
基本使用
package threadlocaltest;
public class Main {
// 全局静态对象,一直被强引用,内部的值永远不会被回收,除非线程被回收,该线程内部的ThreadLocal也会被回收
private static final ThreadLocal<String> userLocal = new ThreadLocal<>();
private static final ThreadLocal<String> domainLocal = new ThreadLocal<>();
public String getCurrentUser() {
return userLocal.get();
}
public void setCurrentUser(String str) {
userLocal.set(str);
}
public String getDomain() {
return domainLocal.get();
}
public void setDomain(String str) {
domainLocal.set(str);
}
public static void main(String[] args) {
// 获取数据
System.out.println(userLocal.get());
System.out.println(domainLocal.get());
}
}
全局静态是最常用的方式,也有不是全局静态的,运行在方法里的
提示:以下是本篇文章正文内容,下面案例仅供参考
一、set方法分析
往ThreadLocalMap里面添加数据
public void set(T value) {
// 获取当前线程里面的ThreadLocal
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// this是ThreadLocal
map.set(this, value);
else
// 初始化
// t.threadLocals = new ThreadLocalMap(this, firstValue);
createMap(t, value);
}
// 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);
}
// ThreadLocalMap中的set方法
private void set(ThreadLocal<?> key, Object value) {
// entry数组,保存具体数据
Entry[] tab = table;
int len = tab.length;
// ThreadLocal的hashCode对数组长度 - 1 的值进行取余,获取到索引下标
int i = key.threadLocalHashCode & (len-1);
// 循环,如果i位置有值
for (Entry e = tab[i];
e != null;
// i++
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 地址值判断,新值覆盖旧值
if (k == key) {
e.value = value;
return;
}
// key为null说明被回收了,创建一个entry覆盖掉
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 没有命中,创建一个entry放到i的位置
tab[i] = new Entry(key, value);
int sz = ++size;
// 长度达到三分之二时,扩容,新数组长度是原来的2倍
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
二、get方法分析
public T get() {
// 获取当前线程里面的ThreadLocal
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// key是Threadlocal
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 第一次get,将value初始化成null
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
// 如果key不一致,key为null则清理,不为null则return tab[i++]
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)
return e;
if (k == null)
// key被回收,说明是无用数据,回收掉
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
三、remove方法分析
public void remove() {
// 当前线程的ThreadLocal
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;
}
}
}
四、如果要设计ThreadLocal,需要考虑什么问题?
4.1、考虑线程间数据隔离的问题
可以参考java对ThreadLocal的措施,集成在了Thread里
ThreadLocal.ThreadLocalMap threadLocals = null;
4.2、考虑防止内存泄漏的方式
1,什么时候会内存泄漏?
内存就那么大,装满了还往里面装
2,为了防止内存泄漏,我们可以采取一些措施将没用的数据及时回收掉
2.1、那么如何标识这些没用的数据呢?
首先要把没用的数据做一个标识,ThreadLocal中是将key作为了弱引用,这样就可以在外部没有强引用的干预下,直接干掉,就变成了null
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
// 弱引用的key
super(k);
value = v;
}
2.2、标识好后什么时候回收?
容易想到的是,我们在每次put时检查一下,但是为了防止一直不put,我们在get时也检查一下有没有无用数据,更严谨一点,提供remove方法,用户想什么时候删除就什么时候删除,减少废弃数据在内存的停留时间(而不用等到get和set时才清理)
总结
在Threadlocal中,提供了expungeStaleEntry方法将无用的entry置空掉,从而防止内存泄漏
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
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;
}
这个回收方法在get、set、remove、数组扩容时都被使用到
ThreadLocalMap的key使用弱引用本身就是优化内存泄漏的一种手段,在ThreadLocal不是全局静态使用方式时,弱引用会生效将key回收,表示这是一条被废弃的数据,但是value还被entry持有,entry被entry数组持有,所以在get或set时都会触发expungeStaleEntry的回收机制,但是为了减少无用数据在内存里的停留时间,可以调用remove方法加速回收
remove更偏向于一种开发规范,防止极端情况发生
如果是全局静态的ThreadLocal则永远不会回收,如果使用了tomcat线程池,则临时线程销毁时ThreadLocal也会跟随回收
核心线程则不会,下次在put数据时同样的ThreadLocal会覆盖掉旧的