ThreadLocal
java1.5API定义:该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get
或 set
方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal
实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
当摸个对象需要被多个线程访问的时候,会出现线程安全问题。这时使用synchronized关键字加锁,只能有一个线程来使用此变量,但是加锁会影响执行效率。这时使用ThreadLocal来解决这个问题。
当使用ThreadLocal来维护变量时,为每一个使用该变量的线程都提供一个独立的变量副本,使得多个线程在访问变量的时候不会彼此影响。
查看源码,我们以set方法举例。
public class Thread implements Runnable {
....
ThreadLocal.ThreadLocalMap threadLocals = null;//ThreadLocal作为Thread的成员变量
....
}
public class ThreadLocal<T> {
....
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //获取当前线程的threadLocals变量
if (map != null)
map.set(this, value);//为threadLocals赋值,调用ThreadLocalMap中的set()方法
else
createMap(t, value);//创建新的ThreadLocalMap
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
....
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
....
static class ThreadLocalMap {
....
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];//默认16长度的数组
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
private void set(ThreadLocal key, Object value) {
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();//返回此引用对象的指示对象
//判断得到的ThreadLocal和当前的ThreadLocal是否一致
if (k == key) { //一致
e.value = value; //赋值
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//都不正确则重新实例化一个Entry
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
....
}
}
从源码中看出Thread中有一个ThreadLocalMap类型的变量threadLocals,当ThreadLocal需要set数据时,会查看当前线程的threadLocals是否存在,如果存在就直接调用ThreadLocalMap类中的set()方法。ThreadLocalMap类中有一个Entry类(类中包含ThreadLocal类型的K和Object类型的value)和一个Entry[]数组类型的table,set()数据时,需要先查找当前的ThreadLocal,当前的ThreadLocal和传参数进来的ThreadLocal是否一致,如果一致就赋值。
ThreadLocal是如何为每一个线程创建一个变量副本的,下面举一个例子来看一看。例子来源于 博客http://www.cnblogs.com/dolphin0520/p/3920407.html
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
final ThreadLocalTest test = new ThreadLocalTest();
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
// 在这里新建了一个线程
Thread thread1 = new Thread() {
public void run() {
test.set(); // 当这里调用了set方法,进一步调用了ThreadLocal的set方法是,会将ThreadLocal变量存储到该线程的ThreadLocalMap类型的成员变量threadLocals中,注意的是这个threadLocals变量是Thread线程的一个变量,而不是ThreadLocal类的变量。
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
}
代码的输出结果: 1 main 9 Thread-0 1 main