前言
ThreadLocal直译是本地线程,但实际上它的译名是线程局部变量(ThreadLocalVariable)。ThreadLocal诞生的目的是隔离不同线程所使用的变量,官方对它的解释是:
提供了线程局部变量,是独立于变量的初始化副本”,也就是说它可以实现将某一个变量隔离在某个线程内,其它的线程无法访问和使用这个变量。
我们先来做一个测试,先不使用ThreadLocal,创建三个线程:
public class ThreadLocalTest {
public static int num = 0;
public static int numAdd() {
return num++;
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(new MyRunnable());
t1.start();
t2.start();
t3.start();
}
public static class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "-" + ThreadLocalTest.numAdd());
}
}
}
}
执行后发现控制台输出的是:
可以发现线程执行了numAdd()方法,从0-8跑了九次,num从0加到8,也就是说线程之间共享了静态变量,从而导致线程的不安全问题。
然后我们再使用ThreadLocal来进行测试。
public class ThreadLocalTest {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0;
}
};
public static int numAdd() {
threadLocal.set(threadLocal.get()+1);
return threadLocal.get();
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(new MyRunnable());
t1.start();
t2.start();
t3.start();
}
public static class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "-" + numAdd());
}
}
}
}
这里的numAdd方法使用了ThreadLocal的get()方法,这个方法调用了initialValue()方法并设置了返回值为0,通过调用这个方法+1,达到了num++的效果,这时候再看输出的结果。
可以看到,三个不同的线程间相互隔离,变量的取值互不相干,也就是说ThreadLocal使用了不相干的变量,或者说ThreadLocal为每一个线程准备了一个变量副本,那么它是如何实现的呢,我们点进ThreadLocal的源码看看。
这就是ThreadLocal的构成了,主要操作是get()和set()方法:
get() : 返回当前ThreadLocal的值
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
set() : 将当前线程对象的值存入ThreadLocalMap中
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
首先创建了当前Thread的对象,然后存入ThreadLocalMap中,对map进行判断,不为空就将this(当前Threadlocal对象)存入作为key,并获取对应的值,最后是调用了一个setInitialValue()方法去获得初始化的值。
ThreadLocalMap
介绍上面两个方法主要是是为了引出ThreadLocal的实现原理,即ThreadLocalMap的创建和使用。
官方注释中解释道,ThreadLocalMap是一个定制的哈希映射,只适用于维护ThreadLocal的值。在ThreadLocal类之外没有导出操作。类是包私有的,以允许在类线程中声明字段。为了帮助处理非常大且长期使用的用法,哈希表条目对键使用弱引用。
但是,由于不使用引用队列,只有当表开始耗尽空间时,才开始删除陈旧的条目。
点开ThreadLocalMap,可以看到一开始ThreadLocalMap定义了一个用于存储数据的Entry 类。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
这个Entry类继承了弱引用类,众所周知Java有四种引用类型,其中弱引用就是每次JVM进行垃圾回收时,都会回收该对象,保证了ThreadLocal每次拷贝当前线程的值的时候所占的空间能被重新使用。
由get()方法可以得知,ThreadLocalMap的键(key)是ThreadLocal类的实例对象,value为用户的值。
那么ThreadLocalMap的引用是在哪里呢,在上面的set()方法里,调用了getMap()和createMap()方法。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
可以看到这边调用了一个叫threadLocals的属性,点击这个属性发现跳到了Thread类中。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
所以这个属性便是ThreadLocalMap的引用了,那么ThreadLocal的实现原理也就很清晰了:
- 定义了一个ThreadLocalMap内部类,使用的是Map的键值对方式来存取数据,key是ThreadLocal类的实例对象,value为传值。
- 创建新的ThreadLocal对象,调用set()或get()方法时,也就是调用了ThreadLocalMap来进行操作。
- 使用ThreadLocal时,线程所使用的变量是独享的(私有的变量副本),其他线程无法访问,在使用过后(线程结束),这些变量会被GC回收。
使用ThreadLocal的原因
ThreadLocal可以用来把实例变量共享成全局变量,让程序中所有的方法都可以访问到该变量。
由于存到ThreadLocal的变量都是当前线程本身,其他线程无法访问,存到ThreadLocal中只是为了方便在程序中同一个线程之间传递这个变量(和解决线程安全没有关系)。