ThreadLocal 介绍
java.lang.ThreadLocal
ThreadLocal 使用
与synchronized等诸多锁实现变量在多线程的正确性不同,锁机制讲究将共享变量在多线程中进行正确的同步,而ThreadLocal的核心思想是每个线程都有自己的变量,也就是说这些变量其实是‘不共享’的,这样其他线程对这个变量的操作也不会影响当前线程的变量正确性。
ThreadLocal的核心方法
1 | //初始化ThreadLocal的值 |
由于篇幅原因,我们就不看所有代码实现,只看核心方法的大致内容,有兴趣的读者可以去阅读源码。我们一般讲ThreadLocal对象声明为静态变量。initialValue方法可以在初始化的时候指定初始化值,而静态方法withInitial可以初始化值并返回一个已经初始化的ThreadLocal对象。set,get方法很明显是为当前线程绑定线程变量,和获取当前线程绑定的线程变量。而remove方法是将线程变量从当前线程中移除。这些方法无论看起来还是使用起来都相当简单。
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
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 /**
* ThreadLocal测试类
* <p>@Author Kam</p>
* <p>@Date 2018/4/26</p>
* <p>@Version</p>
*/
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
protected String initialValue() {
return "init";
}
};
public void testThreadLocal() throws InterruptedException {
String value = threadLocal.get();
//第一次获取的是初始值
System.out.println("value:"+value);// ==>"init"
threadLocal.set("main value");
value = threadLocal.get();
System.out.println("value:"+value);// ==>"main value"
ValueThread valueThread = new ValueThread("valueThread");
valueThread.start();
valueThread.join();
}
class ValueThread extends Thread{
public ValueThread(String name){
super(name);
}
public void run() {
System.out.println("我的线程名称是:"+this.getName());
System.out.println("我的初始值是:"+threadLocal.get());//==>"init"
threadLocal.set(this.getName());
System.out.println("我现在的值是:"+threadLocal.get());//==>valueThread
}
}
}
从以上示例代码我们可以看出,当我们利用构建函数初始化一个ThreadLocal对象,并赋予初始化值。那么无论是哪个线程在没有set新值得情况下,第一次获取到的都是初始化值。这个应该很好理解。而当Test主线程set了新的值,对于示例中的ValueThread来说仍然是不可见的。这就是ThreadLocal的奥妙之处。同一个ThreadLocal对象对于每个线程来说都拥有自己的线程变量。使得当前线程对这个变量的使用不会影响其他线程。
ThreadLocal 的原理。
ThreadLocal 究竟是怎么实现这种线程变量机制的呢,其实关键在于ThreadLocal的存储:ThreadLocalMap。相信有翻过源码的朋友都可以看见,ThreadLocal内部实现了静态类 ThreadLocalMap。而ThreadLocalMap 的存储结构Entry继承了弱引用类 WeakReference ,同时Entry的 Object 类型的value存储的真正的值。1
2
3
4
5
6
7
8
9
10
11static class ThreadLocalMap{
//继承了弱引用类
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
而每个Thread 都有一个ThreadLocalMap 对象。1
2
3class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
现在大家明白了吧。当set一个值得时候,其实就是获取当前线程的ThreadLocalMap 引用,然后赋值。当然get操作也是如此。

ThreadLocal 存在的问题
因为Entry 是继承弱引用类型,而弱引用类型会在GC的时候递归引用进行回收。而如果线程在使用完之后不可达。Entry 的弱引用key将被回收,而value因为是强引用类型所以会导致很多 key为 null的value,没办法访问这些key为null的ThrealLocal。虽然,ThreadLocal在每次进行get,set,remove方法的时候会进行nullkey的清除,但这还是无法避免内存泄漏的根本问题。所以在此提供良好使用ThreadLocal的两条建议。
1)尽量将ThreadLocal 声明为static.延迟生命周期。
2)每次在线程使用完之后,进行remove操作。