ThreadLocal 可以让用户很方便的保存线程隔离的变量,每个线程只能保存一份,多次set新值会覆盖旧值。下面分几个角度来分析一下ThreadLocal。
使用方法
简单附上使用方法:
public class TestThreadLocal {
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static Object lock = new Object();
public static void main(String[] args) {
threadLocal.set("a string run in main");
Thread t = new Thread(new Runnable() {
@Override
public void run() {
TestThreadLocal.threadLocal.set("a string run in sub");
synchronized (TestThreadLocal.lock){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("sub thread get: "+threadLocal.get());
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (TestThreadLocal.lock){
lock.notify();
}
System.out.println("main thread get: " +threadLocal.get());
}
}
运行结果
main thread get: a string run in main
sub thread get: a string run in sub
Process finished with exit code 0
可以看到两个线程间设置的值互不影响,均可以取出正确值。
源码解析
ThreadLocal主要有3个方法,get,set,remove。我们只要了解了插入的原理,另外两个自然不攻自破,附上set的代码
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
主要结构是ThreadLocalMap,把它的数据部分以及set方法贴出来。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
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;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
使用之前例子中的情况,可以看到,当我们调用set方法时,实际是以threadLocal变量本身和这个“a string run in main”字符串生成一个Entry,根据对象的hash值计算位置,放到table数组中。
一些细节
线程安全性。
ThreadLocal变量是存储在线程对象中的,即对于同一个ThreadLocal变量,在不同的线程中操作时,修改的不是同一个内存空间,因此ThreadLocal是线程安全的类。
泛化存储类型
ThreadLocal<Integer> 和 ThreadLocal<String>都是放在同一个Thread相关联的map中,类型不一样怎么存储?因此需要在调用set方法的时候将存入的对象转为Object对象,在调用get方法的时候再转换回来。
如何保证hash冲突后功能的正确性(重点)
首先我们可以看到THreadLocal处理冲突的算法:如果hash冲突了,ThreadLocal采取的是线性探测法。
考虑set方法:
从hash值的位置往后寻找到最近的一个可用位置(可用即当前位置为null,table结尾位置也不为null的话将从位置0继续查找,直到找到)。
考虑get方法:
调用get方法时,如果hash值对应的位置非空,但存储的引用和当前的ThreadLocal对象不一致,那么还有可能是因为冲突了存储在这个位置以后,从当前位置往后查找,依次对比是否是当前ThreadLocal对象,找到则返回,遇到null则说明没找到。
这对ThreadLocal提出了两点要求:
table不能是满的,否则get一个未set的ThreadLocal对象将死循环。
这个问题好解决,ThreadLocal的rehash因子是2/3,因此无论何时table必定有位置的值是null。具有相同hash值的对象,存储必须是连续的,即中间不能有null间隔开来。这就要求在remove一个对象的时候,需要将这个空位以后直到null中间的值都重新计算位置。
弱引用
java中GC的方法是判断对象的引用计数是否等于0,等于0则回收这个对象。正常情况下我们调用如下代码将产生强引用,两句代码执行过后,threadlocal指向的对象的引用计数是2。弱引用的作用就是不参与引用计数的计算。使用弱引用保证了当使用者将ThreadLocal对象置空或指向一个新的对象时,旧对象能得到被GC的机会。
同时我们也就可以解释ThreadLocal代码中多处replaceStaleEntry,expungeStaleEntry(清除过期节点)的原因。
ThreadLocal<String> threadlocal = new ThreadLocal<>();
ThreadLocal<String> threadlocal2 = threadlocal;
InheritableThreadLocal
InheritableThreadLocal继承自ThreadLocal,它和ThreadLocal不同的是,父线程保存的InheritableThreadLocal变量可以在子线程中存留。这个是如何实现的呢?
从以下Thread类的代码可以看到(最后几行),线程类中有一个成员变量inheritableThreadLocals,在构造函数中,会调用init方法,里面以父类的inheritableThreadLocals对象为模版构造了一份子类的inheritableThreadLocals。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}