参考
ThreadLocal
用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量,保证了多线程环境下数据的独立性
使用
注释写的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class ThreadLocalTest {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) { threadLocal.set("sss");
new Thread(() -> { threadLocal.set("hhh"); System.out.println("new Thread:" + threadLocal.get()); }).start();
System.out.println("main:" + threadLocal.get()); } }
|
原理
每个线程维护了一个 ThreadLocalMap 类型的 threadLocals,存着自己定义的 ThreadLocal 的副本( 看 set 那里 )
1
| ThreadLocal.ThreadLocalMap threadLocals = null;
|
ThreadLocalMap
用一个 Entry 数组来存储 key 和 value
1 2 3 4 5 6 7 8 9 10
| private Entry[] table;
private static final int INITIAL_CAPACITY = 16;
private int threshold;
private void setThreshold(int len) { threshold = len * 2 / 3; }
|
set
- 如果当前位置上的 key 相同,直接将该位置上面的 Entry 的 value 替换成最新的
- 如果当前位置上面的 Entry 的 key 为空, 说明 ThreadLocal 对象已经被回收了, 那么就调用 replaceStaleEntry
- 如果清理完无用条目( ThreadLocal 被回收的条目 ),并且数组中的数据大小 > 阈值的时候,对当前的 Table 进行重新哈希
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| 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(); }
private void rehash() { expungeStaleEntries();
if (size >= threshold - threshold / 4) resize(); }
private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0;
for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; } else { int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } }
setThreshold(newLen); size = count; table = newTab; }
|
set
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
|
get
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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(); }
|
setInitialValue
如果 get 的时候 threadLocals 为空,或者 threadLocals 不为空,但存的变量为空,就会返回初始值
如果不重写 initialValue,那默认就返回 null
1 2 3 4 5 6 7 8 9 10 11
| private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
|
应用场景
应用最多的是 session 管理和数据库链接管理
我们希望通过某个类将状态(例如用户ID、事务ID)与线程关联起来,那么通常在这个类中定义private static类型的ThreadLocal 实例
由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用 ThreadLocal 要大
内存泄漏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class ThreadLocalDemo { static class LocalVariable { private Long[] a = new Long[1024 * 1024]; }
final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>()); final static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<LocalVariable>();
public static void main(String[] args) throws InterruptedException { Thread.sleep(5000 * 4); for (int i = 0; i < 50; ++i) { poolExecutor.execute(new Runnable() { public void run() { localVariable.set(new LocalVariable()); System.out.println("use local varaible" + localVariable.get()); localVariable.remove(); } }); } System.out.println("pool execute over"); } }
|
对于线程池里面不会销毁的线程,里面总会存在着 <ThreadLocal, LocalVariable> 的强引用,因为 final static 修饰的 ThreadLocal 并不会释放,而ThreadLocalMap 对于 Key 虽然是弱引用,但是强引用不会释放,弱引用当然也会一直有值,同时创建的 LocalVariable 对象也不会释放,就造成了内存泄露
ThreadLocal提供了一个清除线程中对象的方法,即 remove,内部实现就是调用 ThreadLocalMap 的 remove
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public void remove() { 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; } } }
|
找到 key 对应的 Entry,并且清除 Entry 的 key( ThreadLocal )置空,随后清除过期的 Entry 即可避免内存泄露