GitHub Viewer
---
title: 吊打Java并发面试官之ThreadLocal
shortTitle: ThreadLocal
description: 吊打Java并发面试官之ThreadLocal
category:
- Java核心
tag:
- Java并发编程
head:
- - meta
- name: keywords
content: Java,并发编程,多线程,Thread,ThreadLocal
---
## ThreadLocal 的简介
在多线程编程中通常解决线程安全的问题时,我们会利用 synchronzed 或者 lock 控制线程对临界区资源的同步顺序,但是这种加锁的方式会让未获取到锁的线程进行阻塞等待,很显然这种方式的时间效率并不是特别好。
**线程安全问题的核心在于多个线程会对同一个临界区共享资源进行操作**,那么,如果每个线程都使用自己的“共享资源”,各自使用各自的,互相不影响到彼此,即多个线程间达到隔离的状态,这样就不会出现线程安全的问题。
事实上,这就是一种“**空间换时间**”的方案,每个线程都拥有自己的“共享资源”无疑会让内存占用大很多,但是由于不需要同步也就减少了线程可能存在的阻塞等待,从而提高时间效率。
虽然 ThreadLocal 并不在 java.util.concurrent 包中而在 java.lang 包中,但我更倾向于把它当作是一种并发容器(虽然真正存放数据的是 ThreadLocalMap)进行归类。
顾名思义,**ThreadLocal 表示线程的“本地变量”,即每个线程都拥有该变量副本,达到人手一份的效果,各用各的,这样就可以避免共享资源的竞争**。
## ThreadLocal 的实现原理
要想学习 ThreadLocal 的实现原理,就必须了解它的几个核心方法,包括怎样存怎样取等等,下面我们一个个来看。
### set() 方法
**set 方法设置当前线程中 ThreadLocal 变量的值**,该方法的源码为:
```java
public void set(T value) {
//1. 获取当前线程实例对象
Thread t = Thread.currentThread();
//2. 通过当前线程实例获取到ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null)
//3. 如果Map不为null,则以当前ThreadLocal实例为key,值为value进行存入
map.set(this, value);
else
//4.map为null,则新建ThreadLocalMap并存入value
createMap(t, value);
}
```
方法的逻辑很清晰,具体请看上面的注释。通过源码我们知道 value 是存放在 ThreadLocalMap 里的,当前先把它理解为一个普普通通的 map 即可,也就是说,**数据 value 是存放在 ThreadLocalMap 这个容器中的,并且是以当前 ThreadLocal 实例为 key 的**。
简单看下 ThreadLocalMap 是什么,有个简单的认识就好,后面会具体说的。
**首先 ThreadLocalMap 是怎样来的**?源码很清楚,是通过`getMap(t)`进行获取:
```java
ThreadLocalMap getMap(Thread t) {
return t.ThreadLocals;
}
```
该方法直接返回当前线程对象 t 的一个成员变量 ThreadLocals:
```java
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap ThreadLocals = null;
```
也就是说**ThreadLocalMap 的引用是作为 Thread 的一个成员变量的,被 Thread 进行维护的**。回过头再来看 set 方法,当 map 为 Null 的时候会通过`createMap(t,value)`方法 new 出来一个:
```java
void createMap(Thread t, T firstValue) {
t.ThreadLocals = new ThreadLocalMap(this, firstValue);
}
```
该方法就是new 一个 ThreadLocalMap 实例对象,然后同样以当前 ThreadLocal 实例作为 key,值为 value 存放到 ThreadLocalMap 中的,然后将当前线程对象的 ThreadLocals 赋值为 ThreadLocalMap 对象。
现在来对 set 方法进行总结一下:
通过当前线程对象 thread 获取该 thread 所维护的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则以 ThreadLocal 实例为 key,值为 value 的键值对存入 ThreadLocalMap,若 ThreadLocalMap 为 null 的话,就新建 ThreadLocalMap,然后再以 ThreadLocal 为键,值为 value 的键值对存入即可。
### get() 方法
**get 方法是获取当前线程中 ThreadLocal 变量的值**,同样的还是来看看源码:
```java
public T get() {
//1. 获取当前线程的实例对象
Thread t = Thread.currentThread();
//2. 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//3. 获取map中当前ThreadLocal实例为key的值的entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//4. 当前entitiy不为null的话,就返回相应的值value
T result = (T)e.value;
return result;
}
}
//5. 若map为null或者entry为null的话通过该方法初始化,并返回该方法返回的value
return setInitialValue();
}
```
弄懂了 set 方法的逻辑,看 get 方法只需要带着逆向思维去看就好,如果是那样存的,反过来去拿就好。代码逻辑请看注释,另外,看下 setInitialValue 主要做了些什么事情?
```java
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;
}
```
这段方法的逻辑和 set 方法几乎一致,另外值得关注的是 initialValue 方法:
```java
protected T initialValue() {
return null;
}
```
这个**方法是 protected 修饰的,也就是说继承 ThreadLocal 的子类可重写该方法,实现赋值为其他的初始值**。关于 get 方法来总结一下:
通过当前线程 thread 实例获取到它所维护的 ThreadLocalMap,然后以当前 ThreadLocal 实例为 key 获取该 map 中的键值对(Entry),如果 Entry 不为 null 则返回 Entry 的 value。如果获取 ThreadLocalMap 为 null 或者 Entry 为 null 的话,就以当前 ThreadLocal 为 Key,value 为 null 存入 map 后,并返回 null。
### remove() 方法
```java
public void remove() {
//1. 获取当前线程的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//2. 从map中删除以当前ThreadLocal实例为key的键值对
m.remove(this);
}
```
get、set 方法实现了存数据和读数据的操作,remove 方法实现了如何删数据的操作。删除数据当然是从 map 中删除数据,先获取与当前线程相关联的 ThreadLocalMap,然后从 map 中删除该 ThreadLocal 实例为 key 的键值对即可。
## ThreadLocalMap 详解
从上面的分析我们已经知道,数据其实都放在了 ThreadLocalMap 中,ThreadLocal 的 get、set 和 remove 方法实际上都是通过 ThreadLocalMap 的 getEntry、set 和 remove 方法实现的。如果想真正全方位的弄懂 ThreadLocal,势必得再对 ThreadLocalMap 做一番理解。
### Entry 数据结构
ThreadLocalMap 是 ThreadLocal 一个静态内部类,和大多数容器一样,内部维护了一个数组(Entry 类型的 table 数组)。
```java
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
```
通过注释可以看出,table 数组的长度为 2 的幂次方。接下来看下 Entry 是什么:
```java
static class Entry extends WeakReference