0%

ThreadLocal总结

文章

的学习笔记

ThreadLocal中填充的变量属于当前线程,与其他线程独立。ThreadLocal为变量在每个线程中都创建了一个副本,每个线程访问自己内部的变量

使用方式👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ThreadLocalTest01 {
public static void main(String[] args) {
ThreadLocal<String> local = new ThreadLocal<>();
Random random = new Random();
//使用Stream新建5个线程
IntStream.range(0, 5).forEach(a-> new Thread(()-> {
//为每一个线程设置相应的local值
local.set(a+" "+random.nextInt(10));
System.out.println("线程和local值分别是 "+ local.get());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start());
}
}
/*线程和local值分别是 0 6
线程和local值分别是 1 4
线程和local值分别是 2 3
线程和local值分别是 4 9
线程和local值分别是 3 5 */

源码分析

set()方法

1
2
3
4
5
6
7
8
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是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* [1] No operations are exported outside of the ThreadLocal class.
* [2] However, since reference queues are not used, stale(旧的) entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {

/**
* Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged(删除) from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;

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

然后是get()源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*/
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()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}

另外还有remove()方法

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

通过查看源码可以总结出ThreadThreadLocalThreadLocalMap三者的关系:

  • ThreadLocalMap:ThreadLocal->Object是在Thread类内持有的,这非常合理,因为一个线程内很可能有多个ThreadLocal变量
  • ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value

.image-20220710222546656

内存泄漏问题

ThreadLocal是一个弱引用

只具有弱引用的对象拥有更短暂的生命周期:在GC线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存

不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象

WeakReference引用本身是强引用,它内部的(T reference)才是真正的弱引用字段,WeakReference只是一个装弱引用的容器而已

于是会出现一个现象:key为null,但value还存在

当线程一直存在(比如线程池),后续继续使用这个线程,ThreadLocalMap会不断增大,继而发生内存泄漏

所以,使用完ThreadLocal后,需要调用remove()方法

ThreadLocalMap

可以发现,set()get()remove()的底层核心逻辑其实在ThreadLocalMap中,这是一个独立的类,并不继承任何类,但是自己实现了HashMap的功能

1
2
3
4
5
6
7
8
9
10
11
12
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16;

/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}

...
}

但是它当中并没有链表,那么如何解决哈希冲突呢?

我们来看它的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
private void set(ThreadLocal<?> key, Object value) {

// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.

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.refersTo(key)) {
e.value = value;
return;
}

if (e.refersTo(null)) { //检测内存泄漏
replaceStaleEntry(key, value, i);
return;
}
}

tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

可以很明显的看到,ThreadLocalMap使用线性探测法解决哈希冲突,下面是getEntry()的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.refersTo(key)) //hit后直接返回
return e;
else
return getEntryAfterMiss(key, i, e);
}

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;

while (e != null) { //被占用就一直找下一个
if (e.refersTo(key))
return e;
if (e.refersTo(null))
expungeStaleEntry(i); //处理内存泄漏
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}

InheritableThreadLocal

1
2
3
4
5
private void test() {    
final ThreadLocal threadLocal = new InheritableThreadLocal();
threadLocal.set("帅得一匹");
Thread t = new Thread(()->Log.i( "张三帅么 =" + threadLocal.get())).start();
}

在主线程中创建InheritableThreadLocal实例,然后在子线程中得到设置的值,实现变量共享

底层原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Thread implements Runnable{
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
if (inheritThreadLocals && parent.inheritableThreadLocals != null) //继承父线程的threadLocals
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
}

应用场景

  1. Spring中使用ThreadLocal实现事务隔离:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public abstract class TransactionSynchronizationManager {
    private static final ThreadLocal<Map<Object, Object>> resources =
    new NamedThreadLocal<>("Transactional resources");

    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
    new NamedThreadLocal<>("Transaction synchronizations");

    private static final ThreadLocal<String> currentTransactionName =
    new NamedThreadLocal<>("Current transaction name");
    ...
    }
  2. Spring Security中实现SecurityContextHolderStrategy接口

    SecurityContextHolder.initializeStrategy()👇

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    private static void initializeStrategy() {
    ...
    if (!StringUtils.hasText(strategyName)) {
    // Set default
    strategyName = MODE_THREADLOCAL;
    }
    if (strategyName.equals(MODE_THREADLOCAL)) {
    strategy = new ThreadLocalSecurityContextHolderStrategy();
    return;
    }
    if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
    strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
    return;
    }
    if (strategyName.equals(MODE_GLOBAL)) {
    strategy = new GlobalSecurityContextHolderStrategy();
    return;
    }
    ...
    }

    SecurityContextHolderStrategy接口共有四种实现类,其中ThreadLocalSecurityContextHolderStrategy和InheritableThreadLocalSecurityContextHolderStrategy中分别封装了ThreadLocal和InheritableThreadLocal对象,以ThreadLocalSecurityContextHolderStrategy为例👇

    1
    2
    3
    4
    final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
    ...
    }
  3. Andriod中使用ThreadLocal保证只有一个Looper对象

    1
    2
    3
    4
    5
    6
    7
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
    throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
    }
  4. 使用ThreadLocal实现过渡传参

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //修改前 
    void work(User user) {
    getInfo(user);
    checkInfo(user);
    setSomeThing(user);
    log(user);
    }

    //修改后
    void work(User user) {
    try{
    threadLocalUser.set(user);
    // 他们内部 User u = threadLocalUser.get(); 就好了
    getInfo();
    checkInfo();
    setSomeThing();
    log();
    } finally {
    threadLocalUser.remove();
    }
    }