提供线程内的局部变量,不同线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度
总结 1. 线程并发:在多线程并发的场景下 2. 传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量,类似于JavaWeb中的域对象 3. 线程隔离:每个线程的变量都是独立的,不会互相影响ThreadLocal的基本使用
常用方法
| 方法声明 | 描述 |
|---|---|
| ThreadLocal() | 创建ThreadLocal对象 |
| public void set(T value) | 设置当前线程绑定的局部变量 |
| public T get() | 获取当前线程绑定的局部变量 |
| public void remove() | 移除当前线程绑定的局部变量 |
使用案例
public class ThreadLocalDemo {
// 设置一个ThreadLocal对象
static ThreadLocal tl = new ThreadLocal<>();
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(()->{
// 往ThreadLocal中添加数据
tl.set(Thread.currentThread().getName());
// 取出ThreadLocal中的数据
System.out.println("线程+"+ finalI +"的名字是:"+tl.get());
}).start();
}
}
}
ThreadLocal类与synchronized关键字的区别
虽然ThreadLocal和synchronized都能处理多线程并发访问变量的问题,但二者的角度和思路都不同
| synchronized | ThreadLocal | |
|---|---|---|
| 原理 | 同步机制采用”以时间换空间“的方式,只提供了一份变量,让不同的线程排队访问 | ThreadLocal采用”以空间换时间“的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而互不干扰 |
| 侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |
代码:Object o = new Object();
特点:垃圾回收时不会回收强引用的对象
应用场景:最普遍的引用
软引用代码:SoftReference
特点:垃圾回收时,如果内存不足,则才会回收软引用的对象
原理:m -->(强) SR -->(软) byte[]
应用场景:非常适合做缓存
弱引用代码:WeakReference
特点:只要发生垃圾回收,就会回收弱引用的对象
原理:m -->(强) WR -->(弱) byte[]
应用场景:防止内存泄漏
在ThreadLocal中的使用
图中,key对ThreadLocal就是一个弱引用
虚引用代码:PhantomReference
特点:几乎等同于没有引用,只有在回收时会给出一个信号,在GC的时候会用到
应用场景:JVM中对直接内存的管理
3、ThreadLocal的内部原理前面讲了这么多废话,终于到了原理解析
ThreadLocal的结构每个Thread中,都维护一个TheadLocalMap,就是一个map结构,而每个TheadLocalMap存放的key-value就是是ThreadLocal-value
这样设计的好处是
- 每个Map存储的Entry数量变少
- 当Thread销毁时,ThreadLocalMap也会随之销毁,减少了内存的使用
- Spring中@transactional的处理
- MyBatis中分页的处理
- …
ThreadLocaltl = new ThreadLocal<>(); tl.set(new Person()); // 以tl为key,Person为value装在了一个map里 public void set(T value) { // 获取当前线程 Thread t = Thread.currentThread(); // 获取当前线程的一个map,这个map叫ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) // 往这个map里添加key-value,key是当前的ThreadLocal,value是传入set()方法里的value map.set(this, value); else createMap(t, value); } // 每new出一个thread对象,都会有一个成员变量threadLocals,get()方法就是用来返回这个threadLocals ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
ThreadLocal的set()的原理,在当前线程的TheadLocalMap里存入调用set()方法的ThreadLocal对象-key,传入set()方法的对象-value
ThreadLocal中map.set()源码private void set(ThreadLocal> key, Object value) {
// Entry数组就是一个key-value对,就是map一条一条的记录
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 整个for就是查找遍历存放Entry的数组table,检查key值是否已存在与table中,或key是否为null
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 将Entry数组放到table中
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
// Entry中的key是一个弱引用的子类
// WR -->(弱) ThreadLocal对象
static class Entry extends WeakReference> {
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
ThreadLocal产生内存泄漏的原因
依然围绕着上图来看,假如我们不用ThreadLocal了,可以将其置为null,但是,线程中的ThreadLocalMap中的key会一直指向ThreadLocal,直到线程结束才会回收,所以,就会产生key的内存泄漏
并且,就算key的值变为null,但整个Entry依然存在,也会产生内存value的内存泄漏,因为key为null之后,value再也无法被访问到,但此时value依然强引用某个对象,所以无法被回收
- 解决key的内存泄漏,ThreadLocal已经帮我们做了,那就是把key的引用设置为弱引用,这样,只要tl不再引用ThreadLocal对象,发生垃圾回收时,key就会被回收掉
- 解决value的内存泄漏,每次get()或set()方法都会检查key是否为null,如果为null,则remove掉整个Entry数组,但依然存在问题,比如长时间不调用get()或set()方法,所以要养成手动remove的好习惯



