在并发编程中,线程安全性是需要保证的。在实现线程安全性的方式中,线程封闭是最简单的方式之一,线程封闭就是指仅在单线程内访问数据。当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。这篇文章讨论的就是跟维持线程封闭相关的一个类:ThreadLocal。
ThreadLocal类位于java.lang包下,作用是能够保存线程局部变量的状态,使得每次访问此变量时都能获得实时的、正确的值。ThreadLocal对象通常用于防止对可变的单例变量或全局变量进行共享。(需要注意的是,跟ThreadLocal关联的只能是对象,不能是基本数据类型。)例如,如果我们打算为每个线程分配一个ID,这个ID应该不能共享,只在对应线程中使用,所以ThreadLocal类就派上了用场。代码如下:
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
public static int get() {
return threadId.get();
}
}
以上代码很简单,只是ThreadLocal类如何使用的一个示例,接下来详细介绍ThreadLocal的原理(注意:介绍类源码时所在JDK版本为1.8)。
首先是底层数据结构,其决定了ThreadLocal为何能保存变量的状态,在内部,这个数据结构为ThreadLocalMap,更准确地说,是在内部的Entry这个类,我们可以类比集合中的Map。源码如下:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
细节问题暂不考虑,可以发现,Entry的构造方法将ThreadLocal对象与需要保存的对象值关联了起来,这就是核心所在。细看源码的话可以发现ThreadLocalMap内创建了Entry数组,其构造行为和HashMap类似,具体情况请看源码。
继续介绍ThreadLocal类的运作原理,首先是方法列表:
get() | 获得当前线程局部变量的值 |
initialValue() | 返回当前线程局部变量的初始值 |
remove() | 移除线程与变量的关联 |
set(T value) | 设置当前线程局部变量的值 |
当第一次调用get()方法时,会调用initialValue()方法,默认返回一个空值。所以在使用ThreadLocal时需要将其子类化并重写此方法,创建需要关联的变量,就如同第一个示例程序那样。而初始化的过程,就是建立新的ThreadLocalMap对象,将ThreadLocal对象与变量关联起来。那么,这又是如何跟线程关联起来呢?线程在Java中对应的是Thread类,在它的源码中,有这么一个字段:
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
这就很清楚了,我们回到之前的创建过程,实际上创建ThreadLocalMap的过程是这样的:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal、Thread、线程局部变量就这么关联了起来。其他的方法语义都很明确,不用过多解释了。
---------------------------------------------------------------------------------------------
以上就是ThreadLocal的基本情况,如果深究的话看源码就知道还是有很多内容的,大家可以看看,这里点到为止。最后我还想说的就是,尽管知道了ThreadLocal类的作用,但实际应用是没这么简单的,比如滥用ThreadLocal的问题等,如何正确的使用还是要在实践中摸索。
参考资料:
《Java并发编程实战》
Java官方文档