ThreadLocal是通过线程隔离的方式防止任务在共享资源上产生冲突, 线程本地存储是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储。

QA

  • 什么是ThreadLocal? 用来解决什么问题的? 说说你对ThreadLocal的理解

  • ThreadLocal是如何实现线程隔离的?
    主要是用到了Thread对象中的一个ThreadLocalMap类型的变量threadLocals

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* ThreadLocal values pertaining to this thread. This map is maintained
    * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
    * InheritableThreadLocal values pertaining to this thread. This map is
    * maintained by the InheritableThreadLocal class.
    */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  • 为什么ThreadLocal会造成内存泄露?

  • 如何解决还有哪些使用ThreadLocal的应用场景?

ThreadLocal内存泄漏问题

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

在java中就是存在已经没有任何引用的对象,但是GC又不能把对象所在的内存回收掉,所以就造成了内存泄漏。

源码分析 ThreadLocal

散列算法 - 斐波那契散列法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private final int threadLocalHashCode = nextHashCode();

private static AtomicInteger nextHashCode = new AtomicInteger();

// 神秘数字 0x61c88647
// 黄金分割点:(√5 - 1) / 2 = 0.6180339887
private static final int HASH_INCREMENT = 0x61c88647;

// 计算哈希
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}

// 获取下标
int i = key.threadLocalHashCode & (len-1);

withInitial

1
2
3
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}

get

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}

set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    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);
}

remove

1
2
3
4
5
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

SuppliedThreadLocal

1
2
3
4
5
6
7
8
9
10
11
12
13
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

private final Supplier<? extends T> supplier;

SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}

@Override
protected T initialValue() {
return supplier.get();
}
}

源码分析 ThreadLocalMap 重要!

Entry - WeakReference弱引用

1
2
3
4
5
6
7
8
9
10
11
12
13
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

private Entry[] table;
}

key 是对 ThreadLocal 的一个弱引用。所以在没有外部强引用下,会发生GC,删除key。

ThreadLocalMap类内部为什么用Entry数组,而不是Entry对象?

一个线程里,ThreadLocalMap是同一个,而不是多个,不管new几次ThreadLocal,ThreadLocalMap在一个线程里就一个。
ThreadLocalMap的引用是在Thread里的,所以它里面的Entry数组存放的是一个线程里new出来的多个ThreadLocal对象。

1
2
// Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;

设置元素 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
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;
// 清理过期key,判断是否扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
  1. key.threadLocalHashCode & (len-1);,斐波那契散列,计算数组下标。
  2. Entry,是一个弱引用对象的实现类,所以在没有外部强引用下,会发生GC,删除key。
  3. for循环判断元素是否存在,当前下标不存在元素时,直接设置元素 tab[i] = new Entry(key, value);
  4. 如果元素存在,则会判断是否key值相等 if (k == key),相等则更新值。
  5. 如果不相等,就到了 replaceStaleEntry,探测式清理过期元素。

元素清理 remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}
}

探测式清理 expungeStaleEntry

探测式清理,是以当前遇到的 GC 元素开始,向后不断的清理。直到遇到 null 为止,才停止 rehash 计算。

Rehash until we encounter null

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
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;
}

启发式清理 cleanSomeSlots

试探的扫描一些单元格,寻找过期元素,也就是被垃圾回收的元素。
当添加新元素或删除另一个过时元素时,将调用此函数。
它执行对数扫描次数,作为不扫描(快速但保留垃圾)和与元素数量成比例的扫描次数之间的平衡,这将找到所有垃圾,但会导致一些插入花费O(n)时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

while 循环中不断的右移进行寻找需要被清理的过期元素,最终都会使用 expungeStaleEntry 进行处理,这里还包括元素的移位。

源码分析 InheritableThreadLocal

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
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* Computes the child's initial value for this inheritable thread-local
* variable as a function of the parent's value at the time the child
* thread is created. This method is called from within the parent
* thread before the child is started.
* <p>
* This method merely returns its input argument, and should be overridden
* if a different behavior is desired.
*
* @param parentValue the parent thread's value
* @return the child thread's initial value
*/
protected T childValue(T parentValue) {
return parentValue;
}

/**
* Get the map associated with a ThreadLocal.
*
* @param t the current thread
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}

/**
* Create the map associated with a ThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the table.
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}

源码分析 Thread

init

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
/**
* Initializes a Thread.
*
* @param g 线程组
* @param target run() 方法被调用的对象
* @param name 新线程的名称
* @param stackSize 新线程所需的堆栈大小,或为零表示要忽略此参数。
* @param acc 要继承的 AccessControlContext,如果为 null,则为 AccessController.getContext()
* @param inheritThreadLocals 如果 {@code true},从构造线程继承可继承线程局部变量的初始值
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();

this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);

// 第一项inheritThreadLocals 是传进来的boolean值,重载时传的是true,
// 第二项就是判断父线程中的inheritableThreadLocals属性是否为空,不为空的话
// 两个条件同时满足,把父线程的inheritableThreadLocals复制给子线程
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
// 则新创建出来的子线程的inheritableThreadLocals 变量就和父线程的inheritableThreadLocals 的内容一样了。
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

this.stackSize = stackSize;

tid = nextThreadID();
}