ThreadLocal与FastThreadLocal
# ThreadLocal
# 简介
ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。 ThreadLocal不能使用基本数据类型,只能使用Object类型。
# 实现
ThreadLocal就是绑定在线程上的,可以让每个线程可以存储隔离的线程安全的数据。
其实现就是在Thread类的本地变量中存储ThreadLocal.ThreadlocalMap一个成员变量。
public class Thread implements Runnable {
......(其他源码)
/*
* 当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal
* 本文主要讨论的就是这个ThreadLocalMap
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal,自父线程集成而来的ThreadLocalMap,
* 主要用于父子线程间ThreadLocal变量的传递
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
然后在设置的时候直接使用当前ThreadLocal变量为key到Thread中的ThreadLocalMap中取值,可以看到这里的ThreadlocalMap是一个hash结构。
public class ThreadLocal{
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
//key为自己声明的threadLocal
map.set(this, value);
else
//创建一个ThreadLocalmap
createMap(t, value);
}
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;
}
}
//setInitialValue就是可以在没有设置当前线程的threadlocal的初始值,可以自己定义默认为null
return setInitialValue();
}
}
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
# InheritableThreadLocal子线程共享
InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量,方便必要信息的进一步传递。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
2
3
4
5
6
7
8
9
10
11
在新建线程的init方法中如果inheritThreadLocals为true则会继承,默认是true.并且是使用InheritableThreadLocal的才能继承,判断条件就是thread.inheritableThreadLocals是不是为null,而InheritableThreadLocal.createMap就是将threadlocalMap重写为了inheritableThreadLocals
示例
@Slf4j
public class InheritableThreadLocalSample {
private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
Thread parent = new Thread(() -> {
threadLocal.set("uncle");
inheritableThreadLocal.set("uncle");
log.info("父线程的值:threadLocal:{},inheritableThreadLocal:{}", threadLocal.get(),inheritableThreadLocal.get());
Thread child = new Thread(() -> {
log.info("父线程的值:threadLocal:{},inheritableThreadLocal:{}", threadLocal.get(),inheritableThreadLocal.get());
},"child thread");
child.start();
},"parent thread");
parent.start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
输出
2020-12-17 17:11:59 [parent thread] INFO com.unclezs.samples.java.thread.threadlocal.InheritableThreadLocalSample #lambda$main$1:18 - 父线程的值:threadLocal:uncle,inheritableThreadLocal:uncle
2020-12-17 17:11:59 [child thread] INFO com.unclezs.samples.java.thread.threadlocal.InheritableThreadLocalSample #lambda$null$0:20 - 父线程的值:threadLocal:null,inheritableThreadLocal:uncle
2
# 内存泄漏问题
使用的时候注意ThreadLocal用完之后没用的要释放掉,以免造成内存泄漏。为什么不释放就可能照成内存泄漏呢?JVM不会回收吗?
首先看看ThreadLocalMap的结构,也是哈希结构,不过是开放地址法解决冲突的
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
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();
//已经存在直接设置value
if (k == key) {
e.value = value;
return;
}
//entity存在,但是弱引用key threadlocal已经被回收了 获取到的key为null
if (k == null) {
//直接覆盖这个已经被回收的key的位置 放入新的
replaceStaleEntry(key, value, i);
return;
}
}
//不存在冲突
tab[i] = new Entry(key, value);
int sz = ++size;
//先清理已经被回收了的,范围是keyindex->size 判断是否需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
}
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
所以这里可以看出来,首先Entity是个弱引用对象,引用对象为ThreadLocal,所以ThreadLocal只要发生了GC了就会被回收,这个时候Entity中的样子就变成了:
key=null,value=xx
虽然key threadlocal被回收了,但是value并没有被回收,而且entity也还是在table数组中强引用占用着内存。所以如果不处理这些数据就有可能造成内存泄漏
你可以想象,我们平时使用线程池,这个线程复用技术,因为ThreadLocal实现还是在Thread中放一个ThreadLocalMap,如果复用了Thread也代表会重复用这个ThreadLocalMap,如果一个线程在使用完ThreadLocal之后,另一个线程又拿过来用,导致ThreadLocalMap里面的一直没有正常释放,甚至越放越多。最终导致每个线程里面的ThreadLocalMap都有大量数据,导致内存泄漏
虽然ThreadLocalMap会在set,get以及resize等方法中对stale slots做自动删除(set以及get不保证所有过期slots会在操作中会被删除,而resize则会删除threadLocalMap中所有的过期slots)。但是最佳的实践还是用put就有对应的remove才行
比如用户登录进行put,用户注销进行remove。
# FastThreadLocal
这个为什么说快呢?难道Hash存取数据还不够快吗?还有更快的吗?那也只有数组了。
这个实现是在Netty中的,用数组代替了 Hash+开发地址法。因为ThreadLocalMap是放在线程的成员变量中的,所以Netty自己继承出来一个FastThread,里面就包含了个InternalThreadLocalMap,别看名字是map其实是数组实现的。
所以具体要使用FastThreadLocal需要使用FastThread才行。