以下源码是基于JDK11。
Get以及Set
一开始,一直在疑惑这个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 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
| public T get() { Thread t = Thread.currentThread(); ThreadLocal.ThreadLocalMap map = this.getMap(t); if (map != null) { ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { T result = e.value; return result; } }
return this.setInitialValue(); }
private T setInitialValue() { T value = this.initialValue(); Thread t = Thread.currentThread(); ThreadLocal.ThreadLocalMap map = this.getMap(t); if (map != null) { map.set(this, value); } else { this.createMap(t, value); }
if (this instanceof TerminatingThreadLocal) { TerminatingThreadLocal.register((TerminatingThreadLocal)this); }
return value; }
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue); }
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocal.ThreadLocalMap map = this.getMap(t); if (map != null) { map.set(this, value); } else { this.createMap(t, value); } }
|
其实中源码中可以看出,get和set操作都是先获取到当前线程,而每一个Thread中有一个变量ThreadLocalMap,然后去该变量中获取对应的元素。也就是说在每一个Thread中存储了对应的变量,这样就避免了并发访问的问题。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的一个内部类,ThreadLocalMap内部还有一个内部类,表示ThreadLocalMap的每一个节点。
首先看ThreadLocalMap的两个构造方法:
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
| static class ThreadLocalMap { ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { this.table = new ThreadLocal.ThreadLocalMap.Entry[16]; int i = firstKey.threadLocalHashCode & 15; this.table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue); this.size = 1; this.setThreshold(16); }
private ThreadLocalMap(ThreadLocal.ThreadLocalMap parentMap) { ThreadLocal.ThreadLocalMap.Entry[] parentTable = parentMap.table; int len = parentTable.length; this.setThreshold(len); this.table = new ThreadLocal.ThreadLocalMap.Entry[len]; ThreadLocal.ThreadLocalMap.Entry[] var4 = parentTable; int var5 = parentTable.length;
for(int var6 = 0; var6 < var5; ++var6) { ThreadLocal.ThreadLocalMap.Entry e = var4[var6]; if (e != null) { ThreadLocal<Object> key = (ThreadLocal)e.get(); if (key != null) { Object value = key.childValue(e.value); ThreadLocal.ThreadLocalMap.Entry c = new ThreadLocal.ThreadLocalMap.Entry(key, value); int h; for(h = key.threadLocalHashCode & len - 1; this.table[h] != null; h = nextIndex(h, len)) {} this.table[h] = c; ++this.size; } } } }
static class Entry extends WeakReference<ThreadLocal<?>> { Object value;
Entry(ThreadLocal<?> k, Object v) { super(k); this.value = v; } } }
|
这里的Entry,就是对应存储的具体值,比如说如下代码
1 2
| ThreadLocal<City> threadLocal = new ThreadLocal<>();
|
每一个Thread有一个变量ThreadLocalMap,在创建该变量时,需要传入首个key以及value,其中key是一个ThreadLocal。
为什么要传入这个ThreadLocal呢?
我的理解是ThreadLocal要与对应的Thread绑定。换句话说,我们在使用ThreadLocal时,一般都是在多个线程中通过ThreadLocal来获取一个本该是共享的变量,而在ThreadLocal内部,它会先获取到当前线程,然后调用该线程的ThreadLocalMap来返回线程所需要的变量。所以,创建ThreadLocalMap时就需要传入一个ThreadLocal,方便后续获取值。
Get以及Set
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & this.table.length - 1; ThreadLocal.ThreadLocalMap.Entry e = this.table[i]; return e != null && e.get() == key ? e : this.getEntryAfterMiss(key, i, e); }
private ThreadLocal.ThreadLocalMap.Entry getEntryAfterMiss(ThreadLocal<?> key, int i, ThreadLocal.ThreadLocalMap.Entry e) { ThreadLocal.ThreadLocalMap.Entry[] tab = this.table;
for(int len = tab.length; e != null; e = tab[i]) { ThreadLocal<?> k = (ThreadLocal)e.get(); if (k == key) { return e; }
if (k == null) { this.expungeStaleEntry(i); } else { i = nextIndex(i, len); } }
return null; }
private int expungeStaleEntry(int staleSlot) { ThreadLocal.ThreadLocalMap.Entry[] tab = this.table; int len = tab.length; tab[staleSlot].value = null; tab[staleSlot] = null; --this.size;
ThreadLocal.ThreadLocalMap.Entry e; int i; for(i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = (ThreadLocal)e.get(); if (k == null) { e.value = null; tab[i] = null; --this.size; } else { int h = k.threadLocalHashCode & len - 1; if (h != i) { for(tab[i] = null; tab[h] != null; h = nextIndex(h, len)) { }
tab[h] = e; } } }
return i; }
private void set(ThreadLocal<?> key, Object value) { ThreadLocal.ThreadLocalMap.Entry[] tab = this.table; int len = tab.length; int i = key.threadLocalHashCode & len - 1;
for(ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = (ThreadLocal)e.get(); if (k == key) { e.value = value; return; }
if (k == null) { this.replaceStaleEntry(key, value, i); return; } }
tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value); int sz = ++this.size; if (!this.cleanSomeSlots(i, sz) && sz >= this.threshold) { this.rehash(); }
}
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { ThreadLocal.ThreadLocalMap.Entry[] tab = this.table; int len = tab.length; int slotToExpunge = staleSlot;
ThreadLocal.ThreadLocalMap.Entry e; int i; for(i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) { if (e.get() == null) { slotToExpunge = i; } }
for(i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = (ThreadLocal)e.get(); if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; if (slotToExpunge == staleSlot) { slotToExpunge = i; }
this.cleanSomeSlots(this.expungeStaleEntry(slotToExpunge), len); return; }
if (k == null && slotToExpunge == staleSlot) { slotToExpunge = i; } }
tab[staleSlot].value = null; tab[staleSlot] = new ThreadLocal.ThreadLocalMap.Entry(key, value); if (slotToExpunge != staleSlot) { this.cleanSomeSlots(this.expungeStaleEntry(slotToExpunge), len); }
}
|
这里,ThreadLocalMap是用Entry[]去保存value,而Entry继承了WeakReference,查资料得知这里Entry的key是弱引用,意味着当ThreadLocal没有外部强引用时,会被GC回收,而对应的该ThreadLocal对应的value却不会被回收,若当前线程一直没有结束,会存在一条强引用链, value也会一直累加导致内存泄露。
而expungeStaleEntry
方法就是为了解决上述问题。主动去清理key为空的ThreadLocal对应的value。