ThreadLocal

ThreadLocal

简介

image-20230501195017040

1
2
3
4
总结:
1. 线程并发:多线程并发场景下
2. 数据传递:可以使用threadlocal在同一个线程不同组件中传递公共变量
3. 线程隔离:每个线程都是独立互不干扰的

基本使用

常用方法

image-20230501195341209

案例

当前案例是使用thradlocal保存变量信息

如果不创建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
public class MyDemo01 {
ThreadLocal<String> tl = new ThreadLocal<>();
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public static void main(String[] args) {
MyDemo01 demo = new MyDemo01();
for (int i = 0; i < 1000; i++) {
Thread thread = new Thread(
() -> {
demo.setName(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() + "===>" + demo.getName());
}
);
thread.setName("线程" + i);
thread.start();
}
}
}

image-20230501201857442

加上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
public class MyDemo01 {
ThreadLocal<String> tl = new ThreadLocal<>();
private String name;

public String getName() {
return tl.get();
}

public void setName(String name) {
tl.set(name);
}

public static void main(String[] args) {
MyDemo01 demo = new MyDemo01();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(
() -> {
demo.setName(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName() + "===>" + demo.getName());
}
);
thread.setName("线程" + i);
thread.start();
}
}
}

image-20230501202029097

ThreadLocal和synchronized区别

虽然ThreadLocal和synchronized都是处理多线程访问变量问题,但是两者处理方式不一样

image-20230501202712293

image-20230501202744333

ThreadLocal好处

两个突出优势

image-20230501211102843

ThreadLocal内部结构

常见误解

  • 早期设计

image-20230501211211563

  • 现在设计(JDK8)

    image-20230501211329698

set方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程ThreadLocalMap
ThreadLocalMap map = getMap(t);

if (map != null)
//非空将当前的ThreadLocal作为键,value作为值放进ThreadLocalMap里面
map.set(this, value);
else
//为空,创建一个ThreadLocalMap,再将值和键塞入map里面
createMap(t, value);
}

get方法

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
/**
* 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.
*
* @return the current thread's value of this thread-local
*/
public T get() {
//获取ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);

if (map != null) {
获得ThreadLocalMap中的Entry,也就是存放value值的一个对象
ThreadLocalMap.Entry e = map.getEntry(this);
//不为空,取出这个值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//为空,执行这个方法,将null写进ThreadLocalMap
return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}


static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

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

protected T initialValue() {
return null;
}

代码执行流程

image-20230501214517295

remove方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void remove() {
//获取当前ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());、
//m不为空,就去除这个被维护的对象的entry
if (m != null)
m.remove(this);
}

//ThreadLocalMap中的remove方法,移除entry
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;
}
}
}

initialValue

image-20230501215209651

1
2
3
4
//返回该线程局部变量的初始值
protected T initialValue() {
return null;
}

image-20230501215240374

ThreadLocalMap源码

基本结构

​ ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,使用独立的方式实现了Map的功能,其内部的Entry也是独立实现的

image-20230501215401560

成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 初始化的容量必须是2的整次幂
*/
private static final int INITIAL_CAPACITY = 16;

/**
* 存放数据的table,Entry类的定义在下面分析
*同样,数组的长度必须是2的整次幂
*/
private Entry[] table;

/**
* 数组里面的entry的个数,判断当前使用量是否大于阈值
*/
private int size = 0;

/**
* 进行扩容的阈值,表使用量大于它的时候进行扩容
*/
private int threshold; // Default to 0

存储结构

image-20230501225314068

1
2
3
4
5
6
7
8
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

​ 在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。不过Entry中的key只能是ThreadLocal对象,这点在构造方法中已经限定死了。
​ 另外,Entry继承WeakReference,也就是key (ThreadLocal) 是弱引用,其目的是将ThreadLocal对象的生命周期和线程生命周期解绑。

弱引用和内存泄漏

​ 有些程序员在使用ThreadLocal的过程中会发现有内存泄漏的情况发生,就猜测这个内存泄漏跟Entry中使用了弱引用的key有关系。这个理解其实是不对的。
​ 我们先来面顾这个问题中涉及的几个名词概念,再来分析问题。
(1)内存泄漏相关概念

  • Memory overflow:内存溢出,没有足够的内存提供申请者使用

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

(2) 弱引用相关概念
Java中的引用有4种类型: 强、软、弱、虚。当前这个问题主要涉及到强引用和弱引用:
**强引用(“Strong”Reference)**,就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾回收器就不会回收这种对象。
**弱引用(WeakReference)**,垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

(3)如果key使用强引用
假设ThreadLocalMap中的key使用了强引用,那么会出现内存泄漏吗 ?
此时ThreadLocal的内存图(实线表示强引用)如下:

image-20230501231538711

image-20230501231513664

image-20230501230944367

为什么使用弱引用
根据刚才的分析,我们知道了: 无论使用ThreadLocalMap中的key使用哪种类型引用都无法完全避免内存泄跟使用弱引用没有关系。漏,
要避免内存泄漏有两种方式
1.使用完ThreadLocal,调用其remove方法删除对应的Entry
2.使用完ThreadLocal,当前Thread也随之运行结束

​ 相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的。也就是说,只要记得在使用完ThreadLocal及时的调用remove,无论key是强引用还是弱用都不会有问题,那么为什么key要用弱引用呢 ?
​ 事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null( 也即是ThreadLoca为null) 进行判如果为null的话,那么是会对value置为null的
​ 这就意味着使用完ThreadLocal,CurrentThread依然运行的前提下,就算忘记调用remove方法,弱引用比强引用可以多一层保障:弱引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap调用set,get,remove中的任一方法的时候会被清除,从而避免内存泄漏。