Java中的ThreadLocal这个类的名字乍看容易让人误解,其实应该叫做ThreadLocalVariableKey,这个类的实例是作为key将对应的value存储在当前线程,value才是真正有用的信息。请注意,ThreadLocal对象仅仅是作为线程共享变量map里面的key来使用,其本身并不存储相关信息。一些安全框架如apache shiro和spring security等,都会将登录用户的信息保存到当前线程中,以便在后续的调用中可以调用静态方法获取到,而不是将用户信息作为参数传递到每个被调用的方法中。
下面看一下具体的实现方式和一个例子。
首先java.lang.Thread
有ThreadLocal.ThreadLocalMap
类型的一个成员用以存储当前线程所有的ThreadLocal
变量
public class Thread implements Runnable {
... ...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
... ...
}
值得注意的是,Thread类本身并不提供方法来获取ThreadLocal变量,ThreadLocal对应的变量值的获取需要通过ThreaLocal对象本身的get方法。
以apache shiro为例,
SecurityUtils.getSubject().getPrincipal()
能获取当前线程的principal(其实往往就是login user),注意SecurityUtils.getSubject()是静态方法。下面看一下具体的调用栈
![](https://i-blog.csdnimg.cn/blog_migrate/08a6940b34e3f145a4dc60efaa5f6440.png)
ThreadContext的resources是一个ThreadLocal的变量,其本身就是一个map。getSubject最终就是获取resources这个map里面字符串常量
SUBJECT_KEY
对应的value。可以看到resources是final的,其hash code不会更改,因此可以作为map的key使用。
下面看一下ThreadLocal的实现,如何将ThreadLocal对象作为key通过get方法获取到对应value。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 获取Thread中所有的ThreadLocal变量的map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//以调用get方法的对象this为参数,获取map中对应的value
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
map.getEntry(this);
就是以ThreadLocal对象为key获取对应的value。
下面的示例代码是从网上看到的,虽然有点绕,但能够帮助理解ThreadLocal。刚开始看以为输出是乱序的1-9这九个数字。
public class SequenceNumber{
// ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private final static ThreadLocal<Integer> seqNumVar = new ThreadLocal<Integer>()
{
public Integer initialValue()
{
return 0;
}
};
// ②获取下一个序列值
public int getNextNum()
{
seqNumVar.set(seqNumVar.get() + 1);
return seqNumVar.get();
}
public static void main(String[] args)
{
SequenceNumber sn = new SequenceNumber();
// ③ 3个线程共享sn,各自产生序列号
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread
{
private SequenceNumber sn;
public TestClient(SequenceNumber sn)
{
this.sn = sn;
}
public void run()
{
for (int i = 0; i < 3; i++)
{// ④每个线程打出3个序列值
System.out.println("thread[" + Thread.currentThread().getName() + "] sn[" + sn.getNextNum() + "]");
}
}
}
}
各个线程的输出次序不考虑的话,输出结果可能是
thread[Thread-2] sn[1]
thread[Thread-0] sn[1]
thread[Thread-1] sn[1]
thread[Thread-1] sn[2]
thread[Thread-1] sn[3]
thread[Thread-2] sn[2]
thread[Thread-2] sn[3]
thread[Thread-0] sn[2]
thread[Thread-0] sn[3]
也就是3个线程的序列分别从1开始,seqNumVar
作为三个线程共用的ThreadLocal,序列值互不影响。原因可以简单描述如下:每个线程输出时,都调用ThreadLocal变量seqNumVar
的get方法,这时当前线程的ThreadLocal变量map中并没有seqNumVar
作key的value,因此在map中添加seqNumVar
-0这组key-value,并返回初始值0。