目录
  1. 1. ThreadLocal示例与原理
    1. 1.1. ThreadLocal与Thread如何绑定
      1. 1.1.1. set
ThreadLocal示例与原理

ThreadLocal示例与原理

  多个线程间共享变量,可能会造成线程不安全的问题,需要加锁来实现线程安全,但是加锁会降低系统的吞吐量
  但是有些变量就不需要线程间共享。比如数据库连接池里的连接,我们可以通过串行线程封闭技术来安全的使用连接池中的连接。一个线程A从连接池中把连接拿走,连接池保证不把该连接给别的线程,线程A同样不会把连接发布出去,用完之后返回给连接池,这样一个连接总是在一个线程中使用,不会同时被两个线程操作。线程A保存数据库连接就可以使用 ThreadLocal 来保存,可以在多个方法中获取操作数据库,用完删除即可。(生产者和消费者模式也是使用串行线程封闭技术,大家可以考虑下)
  ThreadLocal 里的数据,其它线程无法访问,只要使用者不把数据发布出去,就可以安全操作它们。我们来看看如何一个 demo 来看下 ThreadLocal 如何使用:

ThreadLocal与Thread如何绑定

  上文我说过 ThreadLocal 会与它所属的 Thread 绑定,这个绑定是什么意思呢,下面我们来看看 Thread的一处源码:

1
2
/* 与此线程相关的ThreadLocal值.这个映射由ThreadLocal类维护 */
ThreadLocal.ThreadLocalMap threadLocals = null;

  ThreadLocal.ThreadLocalMap 类型的 threadLocals属性是保存与当前线程相关的ThreadLocal实例,该map 由ThreadLocal来维护.下面我来看看ThreadLocalMap到底是个什么?
  ThreadLocalMap 是一个定制的散列映射,只适用于维护线程本地值.没有任何操作被导出到ThreadLocal类之外.类是包私有的,允许在类线程中声明字段.为了帮助处理非常大且长期存在的用法,哈希表条目对键使用WeakReference.但是由于没有使用引用队列,所以只有在表空间不足时才会删除陈旧的条目

1
2
3
4
5
6
7
8
9
10
static class Entry extends WeakReference<ThreadLocal<?>> {
/**与ThreadLocal关联的值。*/
Object value;
//key就是ThreadLocal对象本身,而值就是大家想要保存的数据如数据库连接
Entry(ThreadLocal<?> k, Object v) {
//将k置为弱引用
super(k);
value = v;
}
}

看了源码可知:ThreadLocalMap是以ThreadLocal实例为健,用户要线程私有化的数据为值的散列表,并且健;还是弱引用类型的
  下面我们来讲下ThreadLocal如何与线程关联起来的.ThreadLocal实例在调用set和get的时候,会先获取当前线程的threadLocals属性,判断threadLocals属性是否为空,若不为空则进行获取或者添加操作,否则会创建一个ThreadLocalMap实例赋给当前线程的属性threadLocals;然后往里put一个键值对,当get或set方法时健都是当前ThreadLocal实例,只不过是get时,值为ThreadLocal中initValue方法返回的值,默认为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
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
/**
* 返回此线程局部变量的当前线程副本中的值
* 如果变量没有当前线程的值,则首先将其初始化为调用initialValue方法返回的值
*/
public T get() {
Thread t = Thread.currentThread();
// 获取当前线程的 threadLocals 属性
ThreadLocalMap map = getMap(t);
if (map != null) {
// 若threadLocals属性不为空,以this(当前ThreadLocal)实例为健获取对应的值entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 若已经设置过值或者有初始值就直接返回
T result = (T)e.value;
return result;
}
}
// 当前线程的threadLocals属性为空或者没有设置过值时设置初始值
return setInitialValue();
}

/**
* 获取与给定线程相关联的ThreadLoal散列表
* @param t 当前线程
* @return 返回对应的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

/**
* set方法的变体以建立initialValue.
* 如果用户已覆盖set()方法,请使用它代替set()
* @return 初始值
*/
private T setInitialValue() {
// 调用 initialValue 获取初始值默认为 null
T value = initialValue();
Thread t = Thread.currentThread();
// 获取当前线程的 threadLocals 属性
ThreadLocalMap map = getMap(t);
if (map != null)
// 如果已经创建与当前线程关联的 ThreadLoal 散列表,则直接设值
map.set(this, value);
else
// 创建与当前线程相关的 ThreadLocal 散列表 并设值
createMap(t, value);
return value;
}

/**
* 创建与当前线程关联的ThreadLocal散列表.并将它赋值给给定线程的threadLocals属性
* @param t 当前线程
* @param ThreadLocal散列表第一个Entry的初始值
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 向当前线程的线程私有变量设置指定的值
* 大多数子类将不需要重写这个方法,仅仅依靠initialValue方法来设置线程局部变量的值
*/
public void set(T value) {
Thread t = Thread.currentThread();
// 获取当前线程的 threadLocals 属性
ThreadLocalMap map = getMap(t);
if (map != null)
// 如果已经创建与当前线程关联的ThreadLoal散列表,则直接设值
map.set(this, value);
else
// 创建与当前线程相关的ThreadLocal散列表,并设值
createMap(t, value);
}

下面我们用一张图来概括下线程,线程私有变量以及用户定义的数据之间的关系,加深我们的理解:
ThreadLocal.jpg
上图中Entry中的key是弱引用类型的,因此用户程序使用完ThreadLocal对象之后忘记调用remove方法,下一次GC会把只有一个弱引用的ThreadLocal回收掉,此时key指向null,则无论谁都不能访问到该key对应的value对象,只要线程实例不退出就无法释放,如果value对象占用内存很大,则可能会造成OOM.但是ThreadLocalMap底层会对key为null的value进行清理

文章作者: Eric Liang
文章链接: https://ericql.github.io/2019/11/12/01-Java%E5%9F%BA%E7%A1%80%E7%AF%87/01-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E7%82%B9/ThreadLocal/ThreadLocal%E7%A4%BA%E4%BE%8B%E4%B8%8E%E5%8E%9F%E7%90%86/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Eric Liang
打赏
  • 微信
  • 支付宝