ThreadLocal:线程本地变量副本。
当使用ThreadLocal维护变量时,会为每个使用该变量的线程提供独立的变量副本。
每一个线程都可以独立地改变自己的副本,而不会影响其他线程对应的副本。
public class ThreadLocalDemo {
//初始化变量
private ThreadLocal threadLocal = ThreadLocal.withInitial(()->3);
private Random random = new Random();
class Work implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int oldValue = (int) threadLocal.get();
System.out.println(Thread.currentThread().getName()+"threadLocal old value"+ oldValue);
int newValue = random.nextInt(100);
threadLocal.set(newValue);
System.out.println(Thread.currentThread().getName()+"threadLocal new value"+ newValue);
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
ThreadLocalDemo threadLocalDemo= new ThreadLocalDemo();
executorService.execute(threadLocalDemo.new Work());
executorService.execute(threadLocalDemo.new Work());
executorService.execute(threadLocalDemo.new Work());
executorService.shutdown();
}
}
输出结果:
pool-1-thread-3threadLocal old value3
pool-1-thread-1threadLocal old value3
pool-1-thread-2threadLocal old value3
pool-1-thread-1threadLocal new value90
pool-1-thread-3threadLocal new value89
pool-1-thread-2threadLocal new value30
在使用时,ThreadLocal线程安全。
源码详解
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取线程t内的ThreadLocalMap对象,结构的KV的map结构,维护一个内部类Entry(类似HashMap)
//key是threadLocal对象的引用,value是threadLocal对象的值。
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//不为空,返回ThreadLocalMap.Entry中的值
T result = (T)e.value;
return result;
}
}
//对象为空,进行初始化
return setInitialValue();
}
//返回当前线程对象内的threadLocals属性,即ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//获取对应的Entry
private Entry getEntry(ThreadLocal<?> key) {
//key的hashCode & 1111 (默认16) 即保留key的HashCode的低四位
int i = key.threadLocalHashCode & (table.length - 1);
//获取hash表位置i处的Entry对象
Entry e = table[i];
//e非空,且e的key等于key,说明就是要找的对象,返回
//e.get()为弱引用
if (e != null && e.get() == key)
return e;
else
//继续找
return getEntryAfterMiss(key, i, e);
}
//在hash表对应的位置上没有找到对应的Entry
//采用线性探索法,继续在hash表上找
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
//key相等,直接返回
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);//k为null(可能被回收了),清理失效的key
else
i = nextIndex(i, len);//i变更为hash表上下一个位置
e = tab[i];
}
return null;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 清理staleSlot位置的Entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 调整hash表直到后⾯第⼀个tab[i]为null为⽌
Entry e;
int i;
for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果key为null,将该entry置为null
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 下⾯是新计算⼀下hash值,如果位置与当前位置不同
// 需要重新找⼀个位置放该节点。⽤的也是线性探测法
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
//i变更为hash表上下一个位置
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
//设置初始化值
private T setInitialValue() {
//默认为null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
//初始化线程中的ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//构造方法
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);
}
//设置变量
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//map不为空直接set,不为空,创建map并初始化值
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// key的hashcode & 1111,即保留key的hashcode的低4位
int i = key.threadLocalHashCode & (len-1);
// 如果hash表Entry数组第i个位置的Entry⾮空,继续向后探索
// 因为是通过线性探索法处理hash碰撞的
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
// 更新Entry的值
e.value = value;
return;
}
if (k == null) {
// 需要替换旧的Entry
replaceStaleEntry(key, value, i);
return;
}
}
// 如果hash表i位置没有Entry,直接创建新的Entry并存⼊位置i
tab[i] = new Entry(key, value);
//hash表⻓度加1
int sz = ++size;
// 如果没有可以清除的Entry并且hash表的⻓度⼤于等于扩容阈值
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//替换失效的Entry
//副作⽤是清除两个空的位置间的就的Entry对象
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// 备份以检查当前运⾏中的先前失效的Entry
// 待清除的失效的Entry得位置
int slotToExpunge = staleSlot;
// 从staleSlot位置向前搜索,找到hash表第⼀个Entry为空的位置
for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// 从staleSlot位置向后搜索
// 找到hash表第⼀个Entry为空的位置,结束循环
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 互换tab[i]和entry的位置
// 因为ThreadLocalMap是根据线性探测法来解决冲突的
// 因此可能会出现key的哈希值相同但散落位置不连续的情况
// 为了在⼀定程度上提⾼查找哈希值相同entry节点的效率
// 交换⼀下位置会是更好的选择
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
执⾏清理⼯作
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 如果没有找到key对应的Entry,则创建新的Entry并放⼊staleSlot位置
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 如果还有其他的失效的Entry将其清除
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
/**
* 清理⾮null但是key为null的Entry
* 参数n决定了for循环要执⾏的次数。
* 如果每次循环tab[i]均不需要清理,最多会执⾏logn次。
* 如果有需要清理的节点,就会调⽤expungeStaleEntry()⽅法清理
*/
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
ThreadLocal内存泄漏问题
在线程池中,线程执行完后不被回收,而是返回线程池中。
Thread有个强引用指向ThreadLocalMap,ThreadLocalMap有强引用指向Entry,Entry的key是弱引用是ThreadLocal对象。
如果ThreadLocal使用一次后就不在有任何引用指向它,GC会将ThreadLocal对象回收掉。导致Entry变为{null:value}。此时这个Entry已经无效了,因为key被回收了,而value无法被回收,一直存在内存中。
结论:在执行了ThreadLocal.set()方法之后,一定要记得使用ThreadLocal.remove(),将不要的数据移除掉,避免内存泄漏。